453 lines · 14.7 KB · Java
RawDownload
/* * Copyright (c) 1998, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.awt.geom; import java.awt.geom.Rectangle2D;import java.awt.geom.PathIterator;import java.awt.geom.QuadCurve2D;import java.util.Vector; final class Order2 extends Curve {    private double x0;    private double y0;    private double cx0;    private double cy0;    private double x1;    private double y1;    private double xmin;    private double xmax;     private double xcoeff0;    private double xcoeff1;    private double xcoeff2;    private double ycoeff0;    private double ycoeff1;    private double ycoeff2;     public static void insert(Vector curves, double tmp[],                              double x0, double y0,                              double cx0, double cy0,                              double x1, double y1,                              int direction)    {        int numparams = getHorizontalParams(y0, cy0, y1, tmp);        if (numparams == 0) {            // We are using addInstance here to avoid inserting horisontal            // segments            addInstance(curves, x0, y0, cx0, cy0, x1, y1, direction);            return;        }        // assert(numparams == 1);        double t = tmp[0];        tmp[0] = x0;  tmp[1] = y0;        tmp[2] = cx0; tmp[3] = cy0;        tmp[4] = x1;  tmp[5] = y1;        split(tmp, 0, t);        int i0 = (direction == INCREASING)? 0 : 4;        int i1 = 4 - i0;        addInstance(curves, tmp[i0], tmp[i0 + 1], tmp[i0 + 2], tmp[i0 + 3],                    tmp[i0 + 4], tmp[i0 + 5], direction);        addInstance(curves, tmp[i1], tmp[i1 + 1], tmp[i1 + 2], tmp[i1 + 3],                    tmp[i1 + 4], tmp[i1 + 5], direction);    }     public static void addInstance(Vector curves,                                   double x0, double y0,                                   double cx0, double cy0,                                   double x1, double y1,                                   int direction) {        if (y0 > y1) {            curves.add(new Order2(x1, y1, cx0, cy0, x0, y0, -direction));        } else if (y1 > y0) {            curves.add(new Order2(x0, y0, cx0, cy0, x1, y1, direction));        }    }     /*     * Return the count of the number of horizontal sections of the     * specified quadratic Bezier curve.  Put the parameters for the     * horizontal sections into the specified <code>ret</code> array.     * <p>     * If we examine the parametric equation in t, we have:     *     Py(t) = C0*(1-t)^2 + 2*CP*t*(1-t) + C1*t^2     *           = C0 - 2*C0*t + C0*t^2 + 2*CP*t - 2*CP*t^2 + C1*t^2     *           = C0 + (2*CP - 2*C0)*t + (C0 - 2*CP + C1)*t^2     *     Py(t) = (C0 - 2*CP + C1)*t^2 + (2*CP - 2*C0)*t + (C0)     * If we take the derivative, we get:     *     Py(t) = At^2 + Bt + C     *     dPy(t) = 2At + B = 0     *     2*(C0 - 2*CP + C1)t + 2*(CP - C0) = 0     *     2*(C0 - 2*CP + C1)t = 2*(C0 - CP)     *     t = 2*(C0 - CP) / 2*(C0 - 2*CP + C1)     *     t = (C0 - CP) / (C0 - CP + C1 - CP)     * Note that this method will return 0 if the equation is a line,     * which is either always horizontal or never horizontal.     * Completely horizontal curves need to be eliminated by other     * means outside of this method.     */    public static int getHorizontalParams(double c0, double cp, double c1,                                          double ret[]) {        if (c0 <= cp && cp <= c1) {            return 0;        }        c0 -= cp;        c1 -= cp;        double denom = c0 + c1;        // If denom == 0 then cp == (c0+c1)/2 and we have a line.        if (denom == 0) {            return 0;        }        double t = c0 / denom;        // No splits at t==0 and t==1        if (t <= 0 || t >= 1) {            return 0;        }        ret[0] = t;        return 1;    }     /*     * Split the quadratic Bezier stored at coords[pos...pos+5] representing     * the paramtric range [0..1] into two subcurves representing the     * parametric subranges [0..t] and [t..1].  Store the results back     * into the array at coords[pos...pos+5] and coords[pos+4...pos+9].     */    public static void split(double coords[], int pos, double t) {        double x0, y0, cx, cy, x1, y1;        coords[pos+8] = x1 = coords[pos+4];        coords[pos+9] = y1 = coords[pos+5];        cx = coords[pos+2];        cy = coords[pos+3];        x1 = cx + (x1 - cx) * t;        y1 = cy + (y1 - cy) * t;        x0 = coords[pos+0];        y0 = coords[pos+1];        x0 = x0 + (cx - x0) * t;        y0 = y0 + (cy - y0) * t;        cx = x0 + (x1 - x0) * t;        cy = y0 + (y1 - y0) * t;        coords[pos+2] = x0;        coords[pos+3] = y0;        coords[pos+4] = cx;        coords[pos+5] = cy;        coords[pos+6] = x1;        coords[pos+7] = y1;    }     public Order2(double x0, double y0,                  double cx0, double cy0,                  double x1, double y1,                  int direction)    {        super(direction);        // REMIND: Better accuracy in the root finding methods would        //  ensure that cy0 is in range.  As it stands, it is never        //  more than "1 mantissa bit" out of range...        if (cy0 < y0) {            cy0 = y0;        } else if (cy0 > y1) {            cy0 = y1;        }        this.x0 = x0;        this.y0 = y0;        this.cx0 = cx0;        this.cy0 = cy0;        this.x1 = x1;        this.y1 = y1;        xmin = Math.min(Math.min(x0, x1), cx0);        xmax = Math.max(Math.max(x0, x1), cx0);        xcoeff0 = x0;        xcoeff1 = cx0 + cx0 - x0 - x0;        xcoeff2 = x0 - cx0 - cx0 + x1;        ycoeff0 = y0;        ycoeff1 = cy0 + cy0 - y0 - y0;        ycoeff2 = y0 - cy0 - cy0 + y1;    }     public int getOrder() {        return 2;    }     public double getXTop() {        return x0;    }     public double getYTop() {        return y0;    }     public double getXBot() {        return x1;    }     public double getYBot() {        return y1;    }     public double getXMin() {        return xmin;    }     public double getXMax() {        return xmax;    }     public double getX0() {        return (direction == INCREASING) ? x0 : x1;    }     public double getY0() {        return (direction == INCREASING) ? y0 : y1;    }     public double getCX0() {        return cx0;    }     public double getCY0() {        return cy0;    }     public double getX1() {        return (direction == DECREASING) ? x0 : x1;    }     public double getY1() {        return (direction == DECREASING) ? y0 : y1;    }     public double XforY(double y) {        if (y <= y0) {            return x0;        }        if (y >= y1) {            return x1;        }        return XforT(TforY(y));    }     public double TforY(double y) {        if (y <= y0) {            return 0;        }        if (y >= y1) {            return 1;        }        return TforY(y, ycoeff0, ycoeff1, ycoeff2);    }     public static double TforY(double y,                               double ycoeff0, double ycoeff1, double ycoeff2)    {        // The caller should have already eliminated y values        // outside of the y0 to y1 range.        ycoeff0 -= y;        if (ycoeff2 == 0.0) {            // The quadratic parabola has degenerated to a line.            // ycoeff1 should not be 0.0 since we have already eliminated            // totally horizontal lines, but if it is, then we will generate            // infinity here for the root, which will not be in the [0,1]            // range so we will pass to the failure code below.            double root = -ycoeff0 / ycoeff1;            if (root >= 0 && root <= 1) {                return root;            }        } else {            // From Numerical Recipes, 5.6, Quadratic and Cubic Equations            double d = ycoeff1 * ycoeff1 - 4.0 * ycoeff2 * ycoeff0;            // If d < 0.0, then there are no roots            if (d >= 0.0) {                d = Math.sqrt(d);                // For accuracy, calculate one root using:                //     (-ycoeff1 +/- d) / 2ycoeff2                // and the other using:                //     2ycoeff0 / (-ycoeff1 +/- d)                // Choose the sign of the +/- so that ycoeff1+d                // gets larger in magnitude                if (ycoeff1 < 0.0) {                    d = -d;                }                double q = (ycoeff1 + d) / -2.0;                // We already tested ycoeff2 for being 0 above                double root = q / ycoeff2;                if (root >= 0 && root <= 1) {                    return root;                }                if (q != 0.0) {                    root = ycoeff0 / q;                    if (root >= 0 && root <= 1) {                        return root;                    }                }            }        }        /* We failed to find a root in [0,1].  What could have gone wrong?         * First, remember that these curves are constructed to be monotonic         * in Y and totally horizontal curves have already been eliminated.         * Now keep in mind that the Y coefficients of the polynomial form         * of the curve are calculated from the Y coordinates which define         * our curve.  They should theoretically define the same curve,         * but they can be off by a couple of bits of precision after the         * math is done and so can represent a slightly modified curve.         * This is normally not an issue except when we have solutions near         * the endpoints.  Since the answers we get from solving the polynomial         * may be off by a few bits that means that they could lie just a         * few bits of precision outside the [0,1] range.         *         * Another problem could be that while the parametric curve defined         * by the Y coordinates has a local minima or maxima at or just         * outside of the endpoints, the polynomial form might express         * that same min/max just inside of and just shy of the Y coordinate         * of that endpoint.  In that case, if we solve for a Y coordinate         * at or near that endpoint, we may be solving for a Y coordinate         * that is below that minima or above that maxima and we would find         * no solutions at all.         *         * In either case, we can assume that y is so near one of the         * endpoints that we can just collapse it onto the nearest endpoint         * without losing more than a couple of bits of precision.         */        // First calculate the midpoint between y0 and y1 and choose to        // return either 0.0 or 1.0 depending on whether y is above        // or below the midpoint...        // Note that we subtracted y from ycoeff0 above so both y0 and y1        // will be "relative to y" so we are really just looking at where        // zero falls with respect to the "relative midpoint" here.        double y0 = ycoeff0;        double y1 = ycoeff0 + ycoeff1 + ycoeff2;        return (0 < (y0 + y1) / 2) ? 0.0 : 1.0;    }     public double XforT(double t) {        return (xcoeff2 * t + xcoeff1) * t + xcoeff0;    }     public double YforT(double t) {        return (ycoeff2 * t + ycoeff1) * t + ycoeff0;    }     public double dXforT(double t, int deriv) {        switch (deriv) {        case 0:            return (xcoeff2 * t + xcoeff1) * t + xcoeff0;        case 1:            return 2 * xcoeff2 * t + xcoeff1;        case 2:            return 2 * xcoeff2;        default:            return 0;        }    }     public double dYforT(double t, int deriv) {        switch (deriv) {        case 0:            return (ycoeff2 * t + ycoeff1) * t + ycoeff0;        case 1:            return 2 * ycoeff2 * t + ycoeff1;        case 2:            return 2 * ycoeff2;        default:            return 0;        }    }     public double nextVertical(double t0, double t1) {        double t = -xcoeff1 / (2 * xcoeff2);        if (t > t0 && t < t1) {            return t;        }        return t1;    }     public void enlarge(Rectangle2D r) {        r.add(x0, y0);        double t = -xcoeff1 / (2 * xcoeff2);        if (t > 0 && t < 1) {            r.add(XforT(t), YforT(t));        }        r.add(x1, y1);    }     public Curve getSubCurve(double ystart, double yend, int dir) {        double t0, t1;        if (ystart <= y0) {            if (yend >= y1) {                return getWithDirection(dir);            }            t0 = 0;        } else {            t0 = TforY(ystart, ycoeff0, ycoeff1, ycoeff2);        }        if (yend >= y1) {            t1 = 1;        } else {            t1 = TforY(yend, ycoeff0, ycoeff1, ycoeff2);        }        double eqn[] = new double[10];        eqn[0] = x0;        eqn[1] = y0;        eqn[2] = cx0;        eqn[3] = cy0;        eqn[4] = x1;        eqn[5] = y1;        if (t1 < 1) {            split(eqn, 0, t1);        }        int i;        if (t0 <= 0) {            i = 0;        } else {            split(eqn, 0, t0 / t1);            i = 4;        }        return new Order2(eqn[i+0], ystart,                          eqn[i+2], eqn[i+3],                          eqn[i+4], yend,                          dir);    }     public Curve getReversedCurve() {        return new Order2(x0, y0, cx0, cy0, x1, y1, -direction);    }     public int getSegment(double coords[]) {        coords[0] = cx0;        coords[1] = cy0;        if (direction == INCREASING) {            coords[2] = x1;            coords[3] = y1;        } else {            coords[2] = x0;            coords[3] = y0;        }        return PathIterator.SEG_QUADTO;    }     public String controlPointString() {        return ("("+round(cx0)+", "+round(cy0)+"), ");    }}