13381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard/*
23381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard * Copyright (C) 2016 The Android Open Source Project
33381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard *
43381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard * Licensed under the Apache License, Version 2.0 (the "License");
53381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard * you may not use this file except in compliance with the License.
63381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard * You may obtain a copy of the License at
73381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard *
83381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard *      http://www.apache.org/licenses/LICENSE-2.0
93381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard *
103381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard * Unless required by applicable law or agreed to in writing, software
113381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard * distributed under the License is distributed on an "AS IS" BASIS,
123381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard * See the License for the specific language governing permissions and
143381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard * limitations under the License.
153381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard */
163381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
173381cde9f293c52f195b31b0e4049649db31181aJerome Gaillardpackage android.graphics;
183381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
193381cde9f293c52f195b31b0e4049649db31181aJerome Gaillardimport java.awt.geom.AffineTransform;
203381cde9f293c52f195b31b0e4049649db31181aJerome Gaillardimport java.awt.geom.PathIterator;
213381cde9f293c52f195b31b0e4049649db31181aJerome Gaillardimport java.awt.geom.Rectangle2D;
223381cde9f293c52f195b31b0e4049649db31181aJerome Gaillardimport java.awt.geom.RectangularShape;
233381cde9f293c52f195b31b0e4049649db31181aJerome Gaillardimport java.awt.geom.RoundRectangle2D;
243381cde9f293c52f195b31b0e4049649db31181aJerome Gaillardimport java.util.EnumSet;
253381cde9f293c52f195b31b0e4049649db31181aJerome Gaillardimport java.util.NoSuchElementException;
263381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
273381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard/**
283381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard * Defines a rectangle with rounded corners, where the sizes of the corners
293381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard * are potentially different.
303381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard */
313381cde9f293c52f195b31b0e4049649db31181aJerome Gaillardpublic class RoundRectangle extends RectangularShape {
323381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double x;
333381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double y;
343381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double width;
353381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double height;
363381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double ulWidth;
373381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double ulHeight;
383381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double urWidth;
393381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double urHeight;
403381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double lrWidth;
413381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double lrHeight;
423381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double llWidth;
433381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double llHeight;
443381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
453381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    private enum Zone {
463381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        CLOSE_OUTSIDE,
473381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        CLOSE_INSIDE,
483381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        MIDDLE,
493381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        FAR_INSIDE,
503381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        FAR_OUTSIDE
513381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
523381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
533381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    private final EnumSet<Zone> close = EnumSet.of(Zone.CLOSE_OUTSIDE, Zone.CLOSE_INSIDE);
543381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    private final EnumSet<Zone> far = EnumSet.of(Zone.FAR_OUTSIDE, Zone.FAR_INSIDE);
553381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
563381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    /**
573381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard     * @param cornerDimensions array of 8 floating-point number corresponding to the width and
583381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard     * the height of each corner in the following order: upper-left, upper-right, lower-right,
593381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard     * lower-left. It assumes for the size the same convention as {@link RoundRectangle2D}, that
603381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard     * is that the width and height of a corner correspond to the total width and height of the
613381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard     * ellipse that corner is a quarter of.
623381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard     */
633381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public RoundRectangle(float x, float y, float width, float height, float[] cornerDimensions) {
64d88c717b4e124e435e54bf1542774aa100773c3eDiego Perez        assert cornerDimensions.length == 8 : "The array of corner dimensions must have eight " +
65d88c717b4e124e435e54bf1542774aa100773c3eDiego Perez                    "elements";
663381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
673381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.x = x;
683381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.y = y;
693381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.width = width;
703381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.height = height;
713381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
723381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        float[] dimensions = cornerDimensions.clone();
733381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // If a value is negative, the corresponding corner is squared
743381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        for (int i = 0; i < dimensions.length; i += 2) {
753381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            if (dimensions[i] < 0 || dimensions[i + 1] < 0) {
763381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                dimensions[i] = 0;
773381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                dimensions[i + 1] = 0;
783381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            }
793381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
803381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
813381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double topCornerWidth = (dimensions[0] + dimensions[2]) / 2d;
823381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double bottomCornerWidth = (dimensions[4] + dimensions[6]) / 2d;
833381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double leftCornerHeight = (dimensions[1] + dimensions[7]) / 2d;
843381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double rightCornerHeight = (dimensions[3] + dimensions[5]) / 2d;
853381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
863381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // Rescale the corner dimensions if they are bigger than the rectangle
873381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double scale = Math.min(1.0, width / topCornerWidth);
883381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        scale = Math.min(scale, width / bottomCornerWidth);
893381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        scale = Math.min(scale, height / leftCornerHeight);
903381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        scale = Math.min(scale, height / rightCornerHeight);
913381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
923381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.ulWidth = dimensions[0] * scale;
933381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.ulHeight = dimensions[1] * scale;
943381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.urWidth = dimensions[2] * scale;
953381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.urHeight = dimensions[3] * scale;
963381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.lrWidth = dimensions[4] * scale;
973381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.lrHeight = dimensions[5] * scale;
983381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.llWidth = dimensions[6] * scale;
993381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.llHeight = dimensions[7] * scale;
1003381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
1013381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1023381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    @Override
1033381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double getX() {
1043381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        return x;
1053381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
1063381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1073381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    @Override
1083381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double getY() {
1093381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        return y;
1103381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
1113381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1123381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    @Override
1133381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double getWidth() {
1143381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        return width;
1153381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
1163381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1173381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    @Override
1183381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public double getHeight() {
1193381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        return height;
1203381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
1213381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1223381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    @Override
1233381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public boolean isEmpty() {
1243381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        return (width <= 0d) || (height <= 0d);
1253381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
1263381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1273381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    @Override
1283381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public void setFrame(double x, double y, double w, double h) {
1293381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.x = x;
1303381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.y = y;
1313381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.width = w;
1323381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        this.height = h;
1333381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
1343381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1353381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    @Override
1363381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public Rectangle2D getBounds2D() {
1373381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        return new Rectangle2D.Double(x, y, width, height);
1383381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
1393381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1403381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    @Override
1413381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public boolean contains(double x, double y) {
1423381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (isEmpty()) {
1433381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return false;
1443381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
1453381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1463381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double x0 = getX();
1473381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double y0 = getY();
1483381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double x1 = x0 + getWidth();
1493381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double y1 = y0 + getHeight();
1503381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // Check for trivial rejection - point is outside bounding rectangle
1513381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (x < x0 || y < y0 || x >= x1 || y >= y1) {
1523381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return false;
1533381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
1543381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1553381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double insideTopX0 = x0 + ulWidth / 2d;
1563381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double insideLeftY0 = y0 + ulHeight / 2d;
1573381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (x < insideTopX0 && y < insideLeftY0) {
1583381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            // In the upper-left corner
1593381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return isInsideCorner(x - insideTopX0, y - insideLeftY0, ulWidth / 2d, ulHeight / 2d);
1603381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
1613381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1623381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double insideTopX1 = x1 - urWidth / 2d;
1633381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double insideRightY0 = y0 + urHeight / 2d;
1643381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (x > insideTopX1 && y < insideRightY0) {
1653381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            // In the upper-right corner
1663381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return isInsideCorner(x - insideTopX1, y - insideRightY0, urWidth / 2d, urHeight / 2d);
1673381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
1683381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1693381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double insideBottomX1 = x1 - lrWidth / 2d;
1703381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double insideRightY1 = y1 - lrHeight / 2d;
1713381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (x > insideBottomX1 && y > insideRightY1) {
1723381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            // In the lower-right corner
1733381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return isInsideCorner(x - insideBottomX1, y - insideRightY1, lrWidth / 2d,
1743381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    lrHeight / 2d);
1753381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
1763381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1773381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double insideBottomX0 = x0 + llWidth / 2d;
1783381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double insideLeftY1 = y1 - llHeight / 2d;
1793381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (x < insideBottomX0 && y > insideLeftY1) {
1803381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            // In the lower-left corner
1813381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return isInsideCorner(x - insideBottomX0, y - insideLeftY1, llWidth / 2d,
1823381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    llHeight / 2d);
1833381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
1843381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1853381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // In the central part of the rectangle
1863381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        return true;
1873381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
1883381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1893381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    private boolean isInsideCorner(double x, double y, double width, double height) {
1903381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double squareDist = height * height * x * x + width * width * y * y;
1913381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        return squareDist <= width * width * height * height;
1923381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
1933381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
1943381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    private Zone classify(double coord, double side1, double arcSize1, double side2,
1953381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            double arcSize2) {
1963381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (coord < side1) {
1973381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return Zone.CLOSE_OUTSIDE;
1983381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        } else if (coord < side1 + arcSize1) {
1993381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return Zone.CLOSE_INSIDE;
2003381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        } else if (coord < side2 - arcSize2) {
2013381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return Zone.MIDDLE;
2023381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        } else if (coord < side2) {
2033381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return Zone.FAR_INSIDE;
2043381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        } else {
2053381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return Zone.FAR_OUTSIDE;
2063381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
2073381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
2083381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
2093381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public boolean intersects(double x, double y, double w, double h) {
2103381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (isEmpty() || w <= 0 || h <= 0) {
2113381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return false;
2123381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
2133381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double x0 = getX();
2143381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double y0 = getY();
2153381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double x1 = x0 + getWidth();
2163381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double y1 = y0 + getHeight();
2173381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // Check for trivial rejection - bounding rectangles do not intersect
2183381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (x + w <= x0 || x >= x1 || y + h <= y0 || y >= y1) {
2193381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return false;
2203381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
2213381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
2223381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double maxLeftCornerWidth = Math.max(ulWidth, llWidth) / 2d;
2233381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double maxRightCornerWidth = Math.max(urWidth, lrWidth) / 2d;
2243381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double maxUpperCornerHeight = Math.max(ulHeight, urHeight) / 2d;
2253381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        double maxLowerCornerHeight = Math.max(llHeight, lrHeight) / 2d;
2263381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        Zone x0class = classify(x, x0, maxLeftCornerWidth, x1, maxRightCornerWidth);
2273381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        Zone x1class = classify(x + w, x0, maxLeftCornerWidth, x1, maxRightCornerWidth);
2283381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        Zone y0class = classify(y, y0, maxUpperCornerHeight, y1, maxLowerCornerHeight);
2293381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        Zone y1class = classify(y + h, y0, maxUpperCornerHeight, y1, maxLowerCornerHeight);
2303381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
2313381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // Trivially accept if any point is inside inner rectangle
2323381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (x0class == Zone.MIDDLE || x1class == Zone.MIDDLE || y0class == Zone.MIDDLE || y1class == Zone.MIDDLE) {
2333381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return true;
2343381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
2353381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // Trivially accept if either edge spans inner rectangle
2363381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if ((close.contains(x0class) && far.contains(x1class)) || (close.contains(y0class) &&
2373381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                far.contains(y1class))) {
2383381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return true;
2393381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
2403381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
2413381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // Since neither edge spans the center, then one of the corners
2423381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // must be in one of the rounded edges.  We detect this case if
2433381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // a [xy]0class is 3 or a [xy]1class is 1.  One of those two cases
2443381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // must be true for each direction.
2453381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // We now find a "nearest point" to test for being inside a rounded
2463381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // corner.
2473381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (x1class == Zone.CLOSE_INSIDE && y1class == Zone.CLOSE_INSIDE) {
2483381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            // Potentially in upper-left corner
2493381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            x = x + w - x0 - ulWidth / 2d;
2503381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            y = y + h - y0 - ulHeight / 2d;
2513381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return x > 0 || y > 0 || isInsideCorner(x, y, ulWidth / 2d, ulHeight / 2d);
2523381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
2533381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (x1class == Zone.CLOSE_INSIDE) {
2543381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            // Potentially in lower-left corner
2553381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            x = x + w - x0 - llWidth / 2d;
2563381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            y = y - y1 + llHeight / 2d;
2573381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return x > 0 || y < 0 || isInsideCorner(x, y, llWidth / 2d, llHeight / 2d);
2583381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
2593381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (y1class == Zone.CLOSE_INSIDE) {
2603381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            //Potentially in the upper-right corner
2613381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            x = x - x1 + urWidth / 2d;
2623381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            y = y + h - y0 - urHeight / 2d;
2633381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return x < 0 || y > 0 || isInsideCorner(x, y, urWidth / 2d, urHeight / 2d);
2643381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
2653381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        // Potentially in the lower-right corner
2663381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        x = x - x1 + lrWidth / 2d;
2673381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        y = y - y1 + lrHeight / 2d;
2683381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        return x < 0 || y < 0 || isInsideCorner(x, y, lrWidth / 2d, lrHeight / 2d);
2693381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
2703381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
2713381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    @Override
2723381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public boolean contains(double x, double y, double w, double h) {
2733381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        if (isEmpty() || w <= 0 || h <= 0) {
2743381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            return false;
2753381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        }
2763381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        return (contains(x, y) &&
2773381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                contains(x + w, y) &&
2783381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                contains(x, y + h) &&
2793381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                contains(x + w, y + h));
2803381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
2813381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
2823381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    @Override
2833381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    public PathIterator getPathIterator(final AffineTransform at) {
2843381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        return new PathIterator() {
2853381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            int index;
2863381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
2873381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            // ArcIterator.btan(Math.PI/2)
2883381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            public static final double CtrlVal = 0.5522847498307933;
2893381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            private final double ncv = 1.0 - CtrlVal;
2903381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
2913381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            // Coordinates of control points for Bezier curves approximating the straight lines
2923381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            // and corners of the rounded rectangle.
2933381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            private final double[][] ctrlpts = {
2943381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    {0.0, 0.0, 0.0, ulHeight},
2953381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    {0.0, 0.0, 1.0, -llHeight},
2963381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    {0.0, 0.0, 1.0, -llHeight * ncv, 0.0, ncv * llWidth, 1.0, 0.0, 0.0, llWidth,
2973381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                            1.0, 0.0},
2983381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    {1.0, -lrWidth, 1.0, 0.0},
2993381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    {1.0, -lrWidth * ncv, 1.0, 0.0, 1.0, 0.0, 1.0, -lrHeight * ncv, 1.0, 0.0, 1.0,
3003381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                            -lrHeight},
3013381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    {1.0, 0.0, 0.0, urHeight},
3023381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    {1.0, 0.0, 0.0, ncv * urHeight, 1.0, -urWidth * ncv, 0.0, 0.0, 1.0, -urWidth,
3033381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                            0.0, 0.0},
3043381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    {0.0, ulWidth, 0.0, 0.0},
3053381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    {0.0, ncv * ulWidth, 0.0, 0.0, 0.0, 0.0, 0.0, ncv * ulHeight, 0.0, 0.0, 0.0,
3063381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                            ulHeight},
3073381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    {}
3083381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            };
3093381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            private final int[] types = {
3103381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    SEG_MOVETO,
3113381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    SEG_LINETO, SEG_CUBICTO,
3123381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    SEG_LINETO, SEG_CUBICTO,
3133381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    SEG_LINETO, SEG_CUBICTO,
3143381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    SEG_LINETO, SEG_CUBICTO,
3153381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    SEG_CLOSE,
3163381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            };
3173381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
3183381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            @Override
3193381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            public int getWindingRule() {
3203381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                return WIND_NON_ZERO;
3213381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            }
3223381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
3233381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            @Override
3243381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            public boolean isDone() {
3253381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                return index >= ctrlpts.length;
3263381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            }
3273381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
3283381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            @Override
3293381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            public void next() {
3303381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                index++;
3313381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            }
3323381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
3333381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            @Override
3343381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            public int currentSegment(float[] coords) {
3353381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                if (isDone()) {
3363381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    throw new NoSuchElementException("roundrect iterator out of bounds");
3373381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                }
3383381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                int nc = 0;
3393381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                double ctrls[] = ctrlpts[index];
3403381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                for (int i = 0; i < ctrls.length; i += 4) {
3413381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    coords[nc++] = (float) (x + ctrls[i] * width + ctrls[i + 1] / 2d);
3423381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    coords[nc++] = (float) (y + ctrls[i + 2] * height + ctrls[i + 3] / 2d);
3433381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                }
3443381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                if (at != null) {
3453381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    at.transform(coords, 0, coords, 0, nc / 2);
3463381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                }
3473381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                return types[index];
3483381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            }
3493381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard
3503381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            @Override
3513381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            public int currentSegment(double[] coords) {
3523381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                if (isDone()) {
3533381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    throw new NoSuchElementException("roundrect iterator out of bounds");
3543381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                }
3553381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                int nc = 0;
3563381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                double ctrls[] = ctrlpts[index];
3573381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                for (int i = 0; i < ctrls.length; i += 4) {
3583381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    coords[nc++] = x + ctrls[i] * width + ctrls[i + 1] / 2d;
3593381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    coords[nc++] = y + ctrls[i + 2] * height + ctrls[i + 3] / 2d;
3603381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                }
3613381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                if (at != null) {
3623381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                    at.transform(coords, 0, coords, 0, nc / 2);
3633381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                }
3643381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard                return types[index];
3653381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard            }
3663381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard        };
3673381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard    }
3683381cde9f293c52f195b31b0e4049649db31181aJerome Gaillard}
369