/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.graphics; import android.graphics.Bitmap.Config; import android.graphics.Paint.Style; import android.util.Log; import java.util.ArrayList; /** * The Path class encapsulates compound (multiple contour) geometric paths * consisting of straight line segments, quadratic curves, and cubic curves. * It can be drawn with canvas.drawPath(path, paint), either filled or stroked * (based on the paint's Style), or it can be used for clipping or to draw * text on a path. */ public class Path { /** * Create an empty path */ public Path() { mNativePath = init1(); } /** * Create a new path, copying the contents from the src path. * * @param src The path to copy from when initializing the new path */ public Path(Path src) { SkPath valNative = null; if (src != null) { valNative = src.mNativePath; } mNativePath = init2(valNative); } /** * Clear any lines and curves from the path, making it empty. * This does NOT change the fill-type setting. */ public void reset() { native_reset(mNativePath); } /** * Rewinds the path: clears any lines and curves from the path but * keeps the internal data structure for faster reuse. */ public void rewind() { native_rewind(mNativePath); } /** Replace the contents of this with the contents of src. */ public void set(Path src) { if (this != src) { native_set(mNativePath, src.mNativePath); } } /** Enum for the ways a path may be filled */ public static class FillType { // these must match the values in SkPath.h public static final FillType WINDING = new FillType(0); public static final FillType EVEN_ODD = new FillType(1); public static final FillType INVERSE_WINDING = new FillType (2); public static final FillType INVERSE_EVEN_ODD = new FillType(3); FillType(int ni) { nativeInt = ni; } final int nativeInt; public static FillType valueOf(String value) { if ("WINDING".equals(value)) { return FillType.WINDING; } else if ("EVEN_ODD".equals(value)) { return FillType.EVEN_ODD; } else if ("INVERSE_WINDING".equals(value)) { return FillType.INVERSE_WINDING; } else if ("INVERSE_EVEN_ODD".equals(value)) { return FillType.INVERSE_EVEN_ODD; } else { return null; } } public static FillType[] values() { FillType[] filltype = new FillType[4]; filltype[0] = FillType.WINDING; filltype[1] = FillType.EVEN_ODD; filltype[2] = FillType.INVERSE_WINDING; filltype[3] = FillType.INVERSE_EVEN_ODD; return filltype; } } // these must be in the same order as their native values private static final FillType[] sFillTypeArray = { FillType.WINDING, FillType.EVEN_ODD, FillType.INVERSE_WINDING, FillType.INVERSE_EVEN_ODD }; /** * Return the path's fill type. This defines how "inside" is * computed. The default value is WINDING. * * @return the path's fill type */ public FillType getFillType() { return sFillTypeArray[native_getFillType(mNativePath)]; } /** * Set the path's fill type. This defines how "inside" is computed. * * @param ft The new fill type for this path */ public void setFillType(FillType ft) { native_setFillType(mNativePath, ft.nativeInt); } /** * Returns true if the filltype is one of the INVERSE variants * * @return true if the filltype is one of the INVERSE variants */ public boolean isInverseFillType() { final int ft = native_getFillType(mNativePath); return (ft & 2) != 0; } /** * Toggles the INVERSE state of the filltype */ public void toggleInverseFillType() { int ft = native_getFillType(mNativePath); ft ^= 2; native_setFillType(mNativePath, ft); } /** * Returns true if the path is empty (contains no lines or curves) * * @return true if the path is empty (contains no lines or curves) */ public boolean isEmpty() { return native_isEmpty(mNativePath); } /** * Returns true if the path specifies a rectangle. If so, and if rect is * not null, set rect to the bounds of the path. If the path does not * specify a rectangle, return false and ignore rect. * * @param rect If not null, returns the bounds of the path if it specifies * a rectangle * @return true if the path specifies a rectangle */ public boolean isRect(RectF rect) { return native_isRect(mNativePath, rect); } /** * Compute the bounds of the control points of the path, and write the * answer into bounds. If the path contains 0 or 1 points, the bounds is * set to (0,0,0,0) * * @param bounds Returns the computed bounds of the path's control points. * @param exact This parameter is no longer used. */ public void computeBounds(RectF bounds, boolean exact) { native_computeBounds(mNativePath, bounds); } /** * Hint to the path to prepare for adding more points. This can allow the * path to more efficiently allocate its storage. * * @param extraPtCount The number of extra points that may be added to this * path */ public void incReserve(int extraPtCount) { // We can just ignore this. //native_incReserve(mNativePath, extraPtCount); } /** * Set the beginning of the next contour to the point (x,y). * * @param x The x-coordinate of the start of a new contour * @param y The y-coordinate of the start of a new contour */ public void moveTo(float x, float y) { native_moveTo(mNativePath, x, y); } /** * Set the beginning of the next contour relative to the last point on the * previous contour. If there is no previous contour, this is treated the * same as moveTo(). * * @param dx The amount to add to the x-coordinate of the end of the * previous contour, to specify the start of a new contour * @param dy The amount to add to the y-coordinate of the end of the * previous contour, to specify the start of a new contour */ public void rMoveTo(float dx, float dy) { native_rMoveTo(mNativePath, dx, dy); } /** * Add a line from the last point to the specified point (x,y). * If no moveTo() call has been made for this contour, the first point is * automatically set to (0,0). * * @param x The x-coordinate of the end of a line * @param y The y-coordinate of the end of a line */ public void lineTo(float x, float y) { native_lineTo(mNativePath, x, y); } /** * Same as lineTo, but the coordinates are considered relative to the last * point on this contour. If there is no previous point, then a moveTo(0,0) * is inserted automatically. * * @param dx The amount to add to the x-coordinate of the previous point on * this contour, to specify a line * @param dy The amount to add to the y-coordinate of the previous point on * this contour, to specify a line */ public void rLineTo(float dx, float dy) { native_rLineTo(mNativePath, dx, dy); } /** * Add a quadratic bezier from the last point, approaching control point * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for * this contour, the first point is automatically set to (0,0). * * @param x1 The x-coordinate of the control point on a quadratic curve * @param y1 The y-coordinate of the control point on a quadratic curve * @param x2 The x-coordinate of the end point on a quadratic curve * @param y2 The y-coordinate of the end point on a quadratic curve */ public void quadTo(float x1, float y1, float x2, float y2) { native_quadTo(mNativePath, x1, y1, x2, y2); } /** * Same as quadTo, but the coordinates are considered relative to the last * point on this contour. If there is no previous point, then a moveTo(0,0) * is inserted automatically. * * @param dx1 The amount to add to the x-coordinate of the last point on * this contour, for the control point of a quadratic curve * @param dy1 The amount to add to the y-coordinate of the last point on * this contour, for the control point of a quadratic curve * @param dx2 The amount to add to the x-coordinate of the last point on * this contour, for the end point of a quadratic curve * @param dy2 The amount to add to the y-coordinate of the last point on * this contour, for the end point of a quadratic curve */ public void rQuadTo(float dx1, float dy1, float dx2, float dy2) { native_rQuadTo(mNativePath, dx1, dy1, dx2, dy2); } /** * Add a cubic bezier from the last point, approaching control points * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been * made for this contour, the first point is automatically set to (0,0). * * @param x1 The x-coordinate of the 1st control point on a cubic curve * @param y1 The y-coordinate of the 1st control point on a cubic curve * @param x2 The x-coordinate of the 2nd control point on a cubic curve * @param y2 The y-coordinate of the 2nd control point on a cubic curve * @param x3 The x-coordinate of the end point on a cubic curve * @param y3 The y-coordinate of the end point on a cubic curve */ public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { native_cubicTo(mNativePath, x1, y1, x2, y2, x3, y3); } /** * Same as cubicTo, but the coordinates are considered relative to the * current point on this contour. If there is no previous point, then a * moveTo(0,0) is inserted automatically. */ public void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { native_rCubicTo(mNativePath, x1, y1, x2, y2, x3, y3); } /** * Append the specified arc to the path as a new contour. If the start of * the path is different from the path's current last point, then an * automatic lineTo() is added to connect the current contour to the * start of the arc. However, if the path is empty, then we call moveTo() * with the first point of the arc. The sweep angle is tread mod 360. * * @param oval The bounds of oval defining shape and size of the arc * @param startAngle Starting angle (in degrees) where the arc begins * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated * mod 360. * @param forceMoveTo If true, always begin a new contour with the arc */ public void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) { native_arcTo(mNativePath, oval, startAngle, sweepAngle, forceMoveTo); } /** * Append the specified arc to the path as a new contour. If the start of * the path is different from the path's current last point, then an * automatic lineTo() is added to connect the current contour to the * start of the arc. However, if the path is empty, then we call moveTo() * with the first point of the arc. * * @param oval The bounds of oval defining shape and size of the arc * @param startAngle Starting angle (in degrees) where the arc begins * @param sweepAngle Sweep angle (in degrees) measured clockwise */ public void arcTo(RectF oval, float startAngle, float sweepAngle) { native_arcTo(mNativePath, oval, startAngle, sweepAngle, false); } /** * Close the current contour. If the current point is not equal to the * first point of the contour, a line segment is automatically added. */ public void close() { native_close(mNativePath); } /** * Specifies how closed shapes (e.g. rects, ovals) are oriented when they * are added to a path. */ public static class Direction { /** clockwise */ public static final Direction CW = new Direction(0); // must match enum in SkPath.h /** counter-clockwise */ public static final Direction CCW = new Direction(1); // must match enum in SkPath.h Direction(int ni) { nativeInt = ni; } final int nativeInt; public static Direction valueOf(String value) { if ("CW".equals(value)) { return Direction.CW; } else if ("CCW".equals(value)) { return Direction.CCW; } else { return null; } } public static Direction[] values() { Direction[] direction = new Direction[2]; direction[0] = Direction.CW; direction[1] = Direction.CCW; return direction; } } /** * Add a closed rectangle contour to the path * * @param rect The rectangle to add as a closed contour to the path * @param dir The direction to wind the rectangle's contour */ public void addRect(RectF rect, Direction dir) { if (rect == null) { throw new NullPointerException("need rect parameter"); } native_addRect(mNativePath, rect, dir.nativeInt); } /** * Add a closed rectangle contour to the path * * @param left The left side of a rectangle to add to the path * @param top The top of a rectangle to add to the path * @param right The right side of a rectangle to add to the path * @param bottom The bottom of a rectangle to add to the path * @param dir The direction to wind the rectangle's contour */ public void addRect(float left, float top, float right, float bottom, Direction dir) { native_addRect(mNativePath, left, top, right, bottom, dir.nativeInt); } /** * Add a closed oval contour to the path * * @param oval The bounds of the oval to add as a closed contour to the path * @param dir The direction to wind the oval's contour */ public void addOval(RectF oval, Direction dir) { if (oval == null) { throw new NullPointerException("need oval parameter"); } native_addOval(mNativePath, oval, dir.nativeInt); } /** * Add a closed circle contour to the path * * @param x The x-coordinate of the center of a circle to add to the path * @param y The y-coordinate of the center of a circle to add to the path * @param radius The radius of a circle to add to the path * @param dir The direction to wind the circle's contour */ public void addCircle(float x, float y, float radius, Direction dir) { native_addCircle(mNativePath, x, y, radius, dir.nativeInt); } /** * Add the specified arc to the path as a new contour. * * @param oval The bounds of oval defining the shape and size of the arc * @param startAngle Starting angle (in degrees) where the arc begins * @param sweepAngle Sweep angle (in degrees) measured clockwise */ public void addArc(RectF oval, float startAngle, float sweepAngle) { if (oval == null) { throw new NullPointerException("need oval parameter"); } native_addArc(mNativePath, oval, startAngle, sweepAngle); } /** * Add a closed round-rectangle contour to the path * * @param rect The bounds of a round-rectangle to add to the path * @param rx The x-radius of the rounded corners on the round-rectangle * @param ry The y-radius of the rounded corners on the round-rectangle * @param dir The direction to wind the round-rectangle's contour */ public void addRoundRect(RectF rect, float rx, float ry, Direction dir) { if (rect == null) { throw new NullPointerException("need rect parameter"); } native_addRoundRect(mNativePath, rect, rx, ry, dir.nativeInt); } /** * Add a closed round-rectangle contour to the path. Each corner receives * two radius values [X, Y]. The corners are ordered top-left, top-right, * bottom-right, bottom-left * * @param rect The bounds of a round-rectangle to add to the path * @param radii Array of 8 values, 4 pairs of [X,Y] radii * @param dir The direction to wind the round-rectangle's contour */ public void addRoundRect(RectF rect, float[] radii, Direction dir) { if (rect == null) { throw new NullPointerException("need rect parameter"); } if (radii.length < 8) { throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values"); } native_addRoundRect(mNativePath, rect, radii, dir.nativeInt); } /** * Add a copy of src to the path, offset by (dx,dy) * * @param src The path to add as a new contour * @param dx The amount to translate the path in X as it is added */ public void addPath(Path src, float dx, float dy) { native_addPath(mNativePath, src.mNativePath, dx, dy); } /** * Add a copy of src to the path * * @param src The path that is appended to the current path */ public void addPath(Path src) { native_addPath(mNativePath, src.mNativePath); } /** * Add a copy of src to the path, transformed by matrix * * @param src The path to add as a new contour */ public void addPath(Path src, Matrix matrix) { native_addPath(mNativePath, src.mNativePath, matrix); } /** * Offset the path by (dx,dy), returning true on success * * @param dx The amount in the X direction to offset the entire path * @param dy The amount in the Y direction to offset the entire path * @param dst The translated path is written here. If this is null, then * the original path is modified. */ public void offset(float dx, float dy, Path dst) { SkPath dstNative = null; if (dst != null) { dstNative = dst.mNativePath; } native_offset(mNativePath, dx, dy, dstNative); } /** * Offset the path by (dx,dy), returning true on success * * @param dx The amount in the X direction to offset the entire path * @param dy The amount in the Y direction to offset the entire path */ public void offset(float dx, float dy) { native_offset(mNativePath, dx, dy); } /** * Sets the last point of the path. * * @param dx The new X coordinate for the last point * @param dy The new Y coordinate for the last point */ public void setLastPoint(float dx, float dy) { native_setLastPoint(mNativePath, dx, dy); } /** * Transform the points in this path by matrix, and write the answer * into dst. If dst is null, then the the original path is modified. * * @param matrix The matrix to apply to the path * @param dst The transformed path is written here. If dst is null, * then the the original path is modified */ public void transform(Matrix matrix, Path dst) { SkPath dstNative = null; if (dst != null) { dstNative = dst.mNativePath; } native_transform(mNativePath, matrix, dstNative); } /** * Transform the points in this path by matrix. * * @param matrix The matrix to apply to the path */ public void transform(Matrix matrix) { native_transform(mNativePath, matrix); } /** * draw the current path into a HTML5 canvas * @param activeCanvas */ public void drawOnCanvas(String activeCanvas, Bitmap bitmap, Paint paint) { native_drawOnCanvas(mNativePath, activeCanvas, bitmap, paint); } protected void finalize() throws Throwable { try { //finalizer(mNativePath); } finally { super.finalize(); } } /*package*/ final int ni() { return /*mNativePath*/ 0; } private class SkPath { /** Specifies that "inside" is computed by a non-zero sum of signed edge crossings */ public static final int kWinding_FillType = 0; /** Specifies that "inside" is computed by an odd number of edge crossings */ public static final int kEvenOdd_FillType = 1; /** Same as Winding, but draws outside of the path, rather than inside */ public static final int kInverseWinding_FillType = 2; /** Same as EvenOdd, but draws outside of the path, rather than inside */ public static final int kInverseEvenOdd_FillType = 3; public static final int kMove_Verb = 0; //!< iter.next returns 1 point public static final int kLine_Verb = 1; //!< iter.next returns 2 points public static final int kQuad_Verb = 2; //!< iter.next returns 3 points public static final int kCubic_Verb = 3; //!< iter.next returns 4 points public static final int kClose_Verb = 4; //!< iter.next returns 1 point (the last point) public static final int kDone_Verb = 5; //!< iter.next returns 0 points /** clockwise direction for adding closed contours */ public static final int kCW_Direction = 0; /** counter-clockwise direction for adding closed contours */ public static final int kCCW_Direction = 1; public static final float SK_PATH_KAPPA = 0.5522847498f; private ArrayList<PointF> fPts; private ArrayList<Integer> fVerbs; private boolean fBoundsIsDirty; private RectF fBounds; private int fFillType; // Workaround: J2S will rename the passed parameter, use this member to pass the // canvas id to j2s native code. private String mCanvasId; private Bitmap mBitmap; private PointF[] mPts; SkPath() { fPts = new ArrayList<PointF>(); fVerbs = new ArrayList<Integer>(); fBoundsIsDirty = true; fFillType = kWinding_FillType; } SkPath(SkPath src) { //this = src; } public void reset() { fPts.clear(); fVerbs.clear(); fBoundsIsDirty = true; } public void rewind() { fPts.clear(); fVerbs.clear(); fBoundsIsDirty = true; } public void set(SkPath src) { if (src != this) { fPts = src.fPts; fVerbs = src.fVerbs; fFillType = src.fFillType; fBoundsIsDirty = src.fBoundsIsDirty; } } public boolean isEmpty() { int count = fVerbs.size(); return count == 0 || (count == 1 && fVerbs.get(0) == kMove_Verb); } public boolean isRect(RectF rect) { Log.e("Path", "unimplemented"); return false; } public PointF getLastPt() { int count = fPts.size(); if (count == 0) { return new PointF(0, 0); } else { return fPts.get(count - 1); } } public void computeBounds(RectF rect) { // TODO: Implement } public void setLastPt(float x, float y) { int count = fPts.size(); if (count == 0) { this.moveTo(x, y); } else { fPts.set(count - 1, new PointF(x, y)); } } public int getFillType() { return fFillType; } public void setFillType(int ft) { if (ft != kWinding_FillType) { Log.e("Path", "Only Winding FillType is supported in HTML5"); } this.fFillType = ft; } public void moveTo(float x, float y) { int vc = fVerbs.size(); PointF pt = new PointF(x, y); if (vc > 0 && fVerbs.get(vc - 1) == kMove_Verb) { fPts.set(fPts.size() - 1, pt); } else { fPts.add(pt); fVerbs.add(kMove_Verb); } fBoundsIsDirty = true; } public void rMoveTo(float x, float y) { PointF pt = this.getLastPt(); this.moveTo(pt.x + x, pt.y + y); } public void lineTo(float x, float y) { if (fVerbs.size() == 0) { fPts.add(new PointF(0, 0)); fVerbs.add(kMove_Verb); } fPts.add(new PointF(x, y)); fVerbs.add(kLine_Verb); fBoundsIsDirty = true; } public void rLineTo(float x, float y) { PointF pt = this.getLastPt(); this.lineTo(pt.x + x, pt.y + y); } public void quadTo(float x1, float y1, float x2, float y2) { if (fVerbs.size() == 0) { fPts.add(new PointF(0, 0)); fVerbs.add(kMove_Verb); } fPts.add(new PointF(x1, y1)); fPts.add(new PointF(x2, y2)); fVerbs.add(kQuad_Verb); fBoundsIsDirty = true; } public void rQuadTo(float x1, float y1, float x2, float y2) { PointF pt = this.getLastPt(); this.quadTo(pt.x + x1, pt.y + y1, pt.x + x2, pt.y + y2); } public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { if (fVerbs.size() == 0) { fPts.add(new PointF(0, 0)); fVerbs.add(kMove_Verb); } fPts.add(new PointF(x1, y1)); fPts.add(new PointF(x2, y2)); fPts.add(new PointF(x3, y3)); fVerbs.add(kCubic_Verb); fBoundsIsDirty = true; } public void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { PointF pt = this.getLastPt(); this.cubicTo(pt.x + x1, pt.y + y1, pt.x + x2, pt.y + y2, pt.x + x3, pt.y + y3); } public void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) { if (oval.width() < 0 || oval.height() < 0) { return; } if (fVerbs.size() == 0) { forceMoveTo = true; } // build_cubics_points build points CCW, Android use CW by default. ArrayList<PointF> points = build_cubics_points(oval, startAngle, -sweepAngle); if (forceMoveTo) { this.moveTo(points.get(0).x, points.get(0).y); } else { this.lineTo(points.get(0).x, points.get(0).y); } for (int i = 1; (i + 3) <= points.size(); i += 3) { this.cubicTo(points.get(i).x, points.get(i).y, points.get(i + 1).x, points.get(i + 1).y, points.get(i + 2).x, points.get(i + 2).y); } } /** * create cubic points for a defined arc * Reference from QT:: QStroker.cpp * @param rect * @param startAngle * @param sweepAngle * @return the point array */ private ArrayList<PointF> build_cubics_points(RectF rect, float startAngle, float sweepAngle) { ArrayList<PointF> result = new ArrayList<PointF>(); float x = rect.left; float y = rect.top; float w = rect.width(); float w2 = rect.width() / 2; float w2k = w2 * SK_PATH_KAPPA; float h = rect.height(); float h2 = rect.height() / 2; float h2k = h2 * SK_PATH_KAPPA; PointF[] points = { // start point new PointF(x + w, y + h2), // 0 -> 270 degrees new PointF(x + w, y + h2 + h2k), new PointF(x + w2 + w2k, y + h), new PointF(x + w2, y + h), // 270 -> 180 degrees new PointF(x + w2 - w2k, y + h), new PointF(x, y + h2 + h2k), new PointF(x, y + h2), // 180 -> 90 degrees new PointF(x, y + h2 - h2k), new PointF(x + w2 - w2k, y), new PointF(x + w2, y), // 90 -> 0 degrees new PointF(x + w2 + w2k, y), new PointF(x + w, y + h2 - h2k), new PointF(x + w, y + h2) }; if (sweepAngle > 360.0f) { sweepAngle = 360.0f; } else if (sweepAngle < -360.0f) { sweepAngle = -360.0f; } // Special case fast paths if (startAngle == 0.0) { if (sweepAngle == 360.0) { result.add(points[12]); for (int i = 11; i >= 0; --i) //curves[(*point_count)++] = points[i]; result.add(points[i]); return result; } else if (sweepAngle == -360.0) { result.add(points[0]); for (int i = 1; i <= 12; ++i) result.add(points[i]); return result; } } int startSegment = (int) Math.floor(startAngle / 90); int endSegment = (int) Math.floor((startAngle + sweepAngle) / 90); float startT = (startAngle - startSegment * 90) / 90; float endT = (startAngle + sweepAngle - endSegment * 90) / 90; int delta = sweepAngle > 0 ? 1 : -1; if (delta < 0) { startT = 1 - startT; endT = 1 - endT; } // avoid empty start segment if (fuzzyIsNull(startT - 1.0f)) { startT = 0; startSegment += delta; } // avoid empty end segment if (fuzzyIsNull(endT)) { endT = 1; endSegment -= delta; } startT = for_arc_angle(startT * 90); endT = for_arc_angle(endT * 90); boolean splitAtStart = !fuzzyIsNull(startT); boolean splitAtEnd = !fuzzyIsNull(endT - 1.0f); int end = endSegment + delta; // empty arc? if (startSegment == end) { int quadrant = 3 - ((startSegment % 4) + 4) % 4; int j = 3 * quadrant; if (delta > 0) { result.add(points[j + 3]); } else { result.add(points[j]); } return result; } PointF startPoint = new PointF(); PointF endPoint = new PointF(); find_ellipse_coords(rect, startAngle, sweepAngle, startPoint, endPoint); result.add(startPoint); for (int i = startSegment; i != end; i += delta) { int quadrant = 3 - ((i % 4) + 4) % 4; int j = 3 * quadrant; Bezier b = new Bezier(); if (delta > 0) b.fromPoints(points[j + 3], points[j + 2], points[j + 1], points[j]); else b.fromPoints(points[j], points[j + 1], points[j + 2], points[j + 3]); // empty arc? if (startSegment == endSegment && fuzzyCompare(startT, endT)) { return result; } if (i == startSegment) { if (i == endSegment && splitAtEnd) b = b.bezierOnInterval(startT, endT); else if (splitAtStart) b = b.bezierOnInterval(startT, 1); } else if (i == endSegment && splitAtEnd) { b = b.bezierOnInterval(0, endT); } // push control points result.add(new PointF(b.x2, b.y2)); result.add(new PointF(b.x3, b.y3)); result.add(new PointF(b.x4, b.y4)); } result.set(result.size() - 1, endPoint); return result; } private class Bezier { private float x1, y1, x2, y2, x3, y3, x4, y4; Bezier() { } public void setValue(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; this.x3 = x3; this.y3 = y3; this.x4 = x4; this.y4 = y4; } public void fromPoints(PointF p1, PointF p2, PointF p3, PointF p4) { x1 = p1.x; y1 = p1.y; x2 = p2.x; y2 = p2.y; x3 = p3.x; y3 = p3.y; x4 = p4.x; y4 = p4.y; } public Bezier bezierOnInterval(float t0, float t1) { if (t0 == 0 && t1 == 1) return this; Bezier bezier = new Bezier(); bezier.setValue(x1, y1, x2, y2, x3, y3, x4, y4); Bezier result = new Bezier(); bezier.parameterSplitLeft(t0, result); float trueT = (t1-t0)/(1-t0); bezier.parameterSplitLeft(trueT, result); return result; } public void parameterSplitLeft(float t, Bezier result) { result.x1 = x1; result.y1 = y1; result.x2 = x1 + t * ( x2 - x1 ); result.y2 = y1 + t * ( y2 - y1 ); result.x3 = x2 + t * ( x3 - x2 ); // temporary holding spot result.y3 = y2 + t * ( y3 - y2 ); // temporary holding spot x3 = x3 + t * ( x4 - x3 ); y3 = y3 + t * ( y4 - y3 ); x2 = result.x3 + t * ( x3 - result.x3); y2 = result.y3 + t * ( y3 - result.y3); result.x3 = result.x2 + t * ( result.x3 - result.x2 ); result.y3 = result.y2 + t * ( result.y3 - result.y2 ); result.x4 = x1 = result.x3 + t * (x2 - result.x3); result.y4 = y1 = result.y3 + t * (y2 - result.y3); } } private boolean fuzzyIsNull(float p) { return Math.abs(p) < 0.00001f; } private boolean fuzzyCompare(float p1, float p2) { return Math.abs(p1 - p2) < 0.00001f * Math.min(Math.abs(p1), Math.abs(p2)); } private float for_arc_angle(float angle) { if (fuzzyIsNull(angle)) return 0; if (fuzzyCompare(angle, 90.0f)) return 1; float radians = (float) (Math.PI * angle / 180); float cosAngle = (float) Math.cos(radians); float sinAngle = (float) Math.sin(radians); // initial guess float tc = angle / 90; // do some iterations of newton's method to approximate cosAngle // finds the zero of the function b.pointAt(tc).x() - cosAngle tc -= ((((2-3*SK_PATH_KAPPA) * tc + 3*(SK_PATH_KAPPA-1)) * tc) * tc + 1 - cosAngle) // value / (((6-9*SK_PATH_KAPPA) * tc + 6*(SK_PATH_KAPPA-1)) * tc); // derivative tc -= ((((2-3*SK_PATH_KAPPA) * tc + 3*(SK_PATH_KAPPA-1)) * tc) * tc + 1 - cosAngle) // value / (((6-9*SK_PATH_KAPPA) * tc + 6*(SK_PATH_KAPPA-1)) * tc); // derivative // initial guess float ts = tc; // do some iterations of newton's method to approximate sinAngle // finds the zero of the function b.pointAt(tc).y() - sinAngle ts -= ((((3*SK_PATH_KAPPA-2) * ts - 6*SK_PATH_KAPPA + 3) * ts + 3*SK_PATH_KAPPA) * ts - sinAngle) / (((9*SK_PATH_KAPPA-6) * ts + 12*SK_PATH_KAPPA - 6) * ts + 3*SK_PATH_KAPPA); ts -= ((((3*SK_PATH_KAPPA-2) * ts - 6*SK_PATH_KAPPA + 3) * ts + 3*SK_PATH_KAPPA) * ts - sinAngle) / (((9*SK_PATH_KAPPA-6) * ts + 12*SK_PATH_KAPPA - 6) * ts + 3*SK_PATH_KAPPA); // use the average of the t that best approximates cosAngle // and the t that best approximates sinAngle float t = 0.5f * (tc + ts); return t; } private void find_ellipse_coords(RectF r, float angle, float length, PointF startPoint, PointF endPoint) { if (r.isEmpty()) { return; } float w2 = r.width() / 2; float h2 = r.height() / 2; float[] angles = { angle, angle + length }; for (int i = 0; i < 2; ++i) { float theta = (float) (angles[i] - 360 * Math.floor(angles[i] / 360)); float t = theta / 90; // truncate int quadrant = (int) (t >= 0 ? Math.floor(t) : Math.ceil(t)); t -= quadrant; t = for_arc_angle(90 * t); // swap x and y? if ((quadrant & 1) != 0) t = 1 - t; float[] coe = new float[4]; BezierCoefficient(t, coe); PointF p = new PointF(coe[0] + coe[1] + coe[2]*SK_PATH_KAPPA, coe[3] + coe[2] + coe[1]*SK_PATH_KAPPA); // left quadrants if (quadrant == 1 || quadrant == 2) p.x = -p.x; // top quadrants if (quadrant == 0 || quadrant == 1) p.y = -p.y; if (i == 0) { startPoint.x = r.centerX() + w2 * p.x; startPoint.y = r.centerY() + h2 * p.y; } else { endPoint.x = r.centerX() + w2 * p.x; endPoint.y = r.centerY() + h2 * p.y; } } } private void BezierCoefficient(float t, float[] coe) { float m_t = 1.0f - t; coe[1] = m_t * m_t; coe[2] = t * t; coe[3] = coe[2] * t; coe[0] = coe[1] * m_t; coe[1] *= 3.0f * t; coe[2] *= 3.0f * m_t; } public void close() { int count = fVerbs.size(); if (count > 0) { switch (fVerbs.get(count - 1).intValue()) { case kLine_Verb: case kQuad_Verb: case kCubic_Verb: fVerbs.add(kClose_Verb); break; default: // don't add a close if the prev wasn't a primitive break; } } } public void addRect(float left, float top, float right, float bottom, int dir) { // TODO SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom); this.moveTo(left, top); if (dir == kCCW_Direction) { this.lineTo(left, bottom); this.lineTo(right, bottom); this.lineTo(right, top); } else { this.lineTo(right, top); this.lineTo(right, bottom); this.lineTo(left, bottom); } this.close(); } public void addOval(RectF oval, int dir) { // TODO autoPathBoundsUpdate float cx = oval.centerX(); float cy = oval.centerY(); float rx = oval.width() / 2; float ry = oval.height() / 2; float sx = rx * SK_PATH_KAPPA; float sy = ry * SK_PATH_KAPPA; this.moveTo(cx + rx, cy); if (dir == kCCW_Direction) { this.cubicTo(cx + rx, cy - sy, cx + sx, cy - ry, cx, cy - ry); this.cubicTo(cx - sx, cy - ry, cx - rx, cy - sy, cx - rx, cy); this.cubicTo(cx - rx, cy + sy, cx - sx, cy + ry, cx, cy + ry); this.cubicTo(cx + sx, cy + ry, cx + rx, cy + sy, cx + rx, cy); } else { this.cubicTo(cx + rx, cy + sy, cx + sx, cy + ry, cx, cy + ry); this.cubicTo(cx - sx, cy + ry, cx - rx, cy + sy, cx - rx, cy); this.cubicTo(cx - rx, cy - sy, cx - sx, cy - ry, cx, cy - ry); this.cubicTo(cx + sx, cy - ry, cx + rx, cy - sy, cx + rx, cy); } this.close(); } public void addCircle(float x, float y, float radius, int dir) { if (radius > 0) { RectF rect = new RectF(x - radius, y - radius, x + radius, y + radius); this.addOval(rect, dir); } } public void addArc(RectF oval, float startAngle, float sweepAngle) { if (oval.isEmpty() || 0 == sweepAngle) { return; } if (sweepAngle >= 360f || sweepAngle <= -360f) { this.addOval(oval, sweepAngle > 0 ? kCW_Direction : kCCW_Direction); } ArrayList<PointF> points = build_cubics_points(oval, startAngle, sweepAngle); this.moveTo(points.get(0).x, points.get(0).y); for (int i = 1; i < points.size(); i+=3) { this.cubicTo(points.get(i).x, points.get(i).y, points.get(i + 1).x, points.get(i + 1).y, points.get(i + 2).x, points.get(i + 2).y); } this.close(); } public void addRoundRect(RectF rect, float rx, float ry, int dir) { float w = rect.width(); float halfW = w / 2; float h = rect.height(); float halfH = h / 2; if (halfW <= 0 || halfH <= 0) { return; } boolean skip_hori = rx >= halfW; boolean skip_vert = ry >= halfH; if (skip_hori && skip_vert) { this.addOval(rect, dir); return; } // TODO :SkAutoPathBoundsUpdate apbu(this, rect); if (skip_hori) { rx = halfW; } else if (skip_vert) { ry = halfH; } float sx = rx * 0.5522848f; float sy = ry * 0.5522848f; this.moveTo(rect.right - rx, rect.top); if (dir == kCCW_Direction) { if (!skip_hori) { this.lineTo(rect.left + rx, rect.top); // top } this.cubicTo(rect.left + rx - sx, rect.top, rect.left, rect.top + ry - sy, rect.left, rect.top + ry); // top-left if (!skip_vert) { this.lineTo(rect.left, rect.bottom - ry); // left } this.cubicTo(rect.left, rect.bottom - ry + sy, rect.left + rx - sx, rect.bottom, rect.left + rx, rect.bottom); // bot-left if (!skip_hori) { this.lineTo(rect.right - rx, rect.bottom); // bottom } this.cubicTo(rect.right - rx + sx, rect.bottom, rect.right, rect.bottom - ry + sy, rect.right, rect.bottom - ry); // bot-right if (!skip_vert) { this.lineTo(rect.right, rect.top + ry); } this.cubicTo(rect.right, rect.top + ry - sy, rect.right - rx + sx, rect.top, rect.right - rx, rect.top); // top-right } else { this.cubicTo(rect.right - rx + sx, rect.top, rect.right, rect.top + ry - sy, rect.right, rect.top + ry); // top-right if (!skip_vert) { this.lineTo(rect.right, rect.bottom - ry); } this.cubicTo(rect.right, rect.bottom - ry + sy, rect.right - rx + sx, rect.bottom, rect.right - rx, rect.bottom); // bot-right if (!skip_hori) { this.lineTo(rect.left + rx, rect.bottom); // bottom } this.cubicTo(rect.left + rx - sx, rect.bottom, rect.left, rect.bottom - ry + sy, rect.left, rect.bottom - ry); // bot-left if (!skip_vert) { this.lineTo(rect.left, rect.top + ry); // left } this.cubicTo(rect.left, rect.top + ry - sy, rect.left + rx - sx, rect.top, rect.left + rx, rect.top); // top-left if (!skip_hori) { this.lineTo(rect.right - rx, rect.top); // top } } this.close(); } public void addRoundRect(RectF rect, float[] rad, int dir) { // abort before we invoke SkAutoPathBoundsUpdate() if (rect.isEmpty()) { return; } // TODO: SkAutoPathBoundsUpdate apbu(this, rect); if (kCW_Direction == dir) { this.add_corner_arc(rect, rad[0], rad[1], 180, dir, true); this.add_corner_arc(rect, rad[2], rad[3], 270, dir, false); this.add_corner_arc(rect, rad[4], rad[5], 0, dir, false); this.add_corner_arc(rect, rad[6], rad[7], 90, dir, false); } else { this.add_corner_arc(rect, rad[0], rad[1], 180, dir, true); this.add_corner_arc(rect, rad[6], rad[7], 90, dir, false); this.add_corner_arc(rect, rad[4], rad[5], 0, dir, false); this.add_corner_arc(rect, rad[2], rad[3], 270, dir, false); } this.close(); } private void add_corner_arc(RectF rect, float rx, float ry, int startAngle, int dir, boolean forceMoveTo) { rx = Math.min(rect.width() / 2, rx); ry = Math.min(rect.height() / 2, ry); RectF r = new RectF(-rx, -ry, rx, ry); r.set(-rx, -ry, rx, ry); switch (startAngle) { case 0: r.offset(rect.right - r.right, rect.bottom - r.bottom); break; case 90: r.offset(rect.left - r.left, rect.bottom - r.bottom); break; case 180: r.offset(rect.left - r.left, rect.top - r.top); break; case 270: r.offset(rect.right - r.right, rect.top - r.top); break; default: Log.e("Path", "unexpected startAngle in add_corner_arc"); } float start = startAngle; float sweep = 90; if (kCCW_Direction == dir) { start += sweep; sweep = -sweep; } this.arcTo(r, start, sweep, forceMoveTo); } public void addPath(SkPath src, float dx, float dy) { Matrix matrix = new Matrix(); matrix.setTranslate(dx, dy); this.addPath(src, matrix); } public void addPath(SkPath src) { Matrix matrix = new Matrix(); matrix.reset(); this.addPath(src, matrix); } private void addPath(SkPath src, Matrix matrix) { PointF[] pts = new PointF[4]; int verb; PathIter iter = new PathIter(src, false); while ((verb = iter.next(pts)) != kDone_Verb) { switch (verb) { case kMove_Verb: float[] mapPts0 = new float[2]; mapPts0[0] = pts[0].x; mapPts0[1] = pts[0].y; matrix.mapPoints(mapPts0); this.moveTo(mapPts0[0], mapPts0[1]); break; case kLine_Verb: float[] mapPts1 = new float[2]; mapPts1[0] = pts[1].x; mapPts1[1] = pts[1].y; matrix.mapPoints(mapPts1); this.lineTo(mapPts1[0], mapPts1[1]); break; case kQuad_Verb: float[] mapPts2 = new float[4]; mapPts2[0] = pts[1].x; mapPts2[1] = pts[1].y; mapPts2[2] = pts[2].x; mapPts2[3] = pts[2].y; matrix.mapPoints(mapPts2); this.quadTo(mapPts2[0], mapPts2[1], mapPts2[2], mapPts2[3]); break; case kCubic_Verb: float[] mapPts3 = new float[6]; mapPts3[0] = pts[1].x; mapPts3[1] = pts[1].y; mapPts3[2] = pts[2].x; mapPts3[3] = pts[2].y; mapPts3[4] = pts[3].x; mapPts3[5] = pts[3].y; matrix.mapPoints(mapPts3); this.cubicTo(mapPts3[0], mapPts3[1], mapPts3[2], mapPts3[3], mapPts3[4], mapPts3[5]); break; case kClose_Verb: this.close(); break; default: Log.e("Path", "unknown verb"); } } } public void transform(Matrix matrix, SkPath dst) { if (dst == null) { dst = this; } if ((matrix.getType() & 0x08 /*kPerspective_Mask*/) != 0) { Log.e("Path", "transform path with perspective has not been implemented yet!"); // SkPath tmp = new SkPath(); // tmp.fFillType = fFillType; // // SkPath::Iter iter(*this, false); // SkPoint pts[4]; // SkPath::Verb verb; // // while ((verb = iter.next(pts)) != kDone_Verb) { // switch (verb) { // case kMove_Verb: // tmp.moveTo(pts[0]); // break; // case kLine_Verb: // tmp.lineTo(pts[1]); // break; // case kQuad_Verb: // subdivide_quad_to(&tmp, pts); // break; // case kCubic_Verb: // subdivide_cubic_to(&tmp, pts); // break; // case kClose_Verb: // tmp.close(); // break; // default: // SkASSERT(!"unknown verb"); // break; // } // } // // dst->swap(tmp); // matrix.mapPoints(dst->fPts.begin(), dst->fPts.count()); } else { // remember that dst might == this, so be sure to check // fBoundsIsDirty before we set it if (!fBoundsIsDirty && matrix.rectStaysRect() && fPts.size() > 1) { // if we're empty, fastbounds should not be mapped matrix.mapRect(dst.fBounds, fBounds); dst.fBoundsIsDirty = false; } else { dst.fBoundsIsDirty = true; } if (this != dst) { dst.fVerbs = fVerbs; //dst->fPts.setCount(fPts.count()); dst.fFillType = fFillType; } float[] dstPts = new float[fPts.size() * 2]; float[] srcPts = new float[fPts.size() * 2]; for (int i = 0; i < fPts.size(); i ++) { srcPts[i * 2 + 0] = fPts.get(i).x; srcPts[i * 2 + 1] = fPts.get(i).y; } matrix.mapPoints(dstPts, srcPts); } } public void drawOnCanvas(String canvas, Bitmap bitmap, Paint paint) { // Workaround this.mCanvasId = canvas; this.mBitmap = bitmap; /** * @j2sNative * var _canvas = null; * if (this.mCanvasId != null) { * _canvas = document.getElementById(this.mCanvasId); * } else { * _canvas = this.mBitmap.mCachedCanvas; * // Update the bitmap cached canvas dirty flag * this.mBitmap.mIsCachedCanvasDirty = true; * } * if (_canvas == null) { * throw "Can't get canvas for this path!"; * } * var ctx = _canvas.getContext("2d"); * ctx.beginPath(); */{} PathIter iter = new PathIter(this, false); this.mPts = new PointF[4]; int verb; while ((verb = iter.next(this.mPts)) != kDone_Verb) { switch (verb) { case kMove_Verb: /** * @j2sNative * ctx.moveTo(this.mPts[0].x, this.mPts[0].y); */{} break; case kLine_Verb: /** * @j2sNative * ctx.lineTo(this.mPts[1].x, this.mPts[1].y); */{} break; case kQuad_Verb: /** * @j2sNative * ctx.quadraticCurveTo(this.mPts[1].x, this.mPts[1].y, this.mPts[2].x, this.mPts[2].y); */{} break; case kCubic_Verb: /** * @j2sNative * ctx.bezierCurveTo(this.mPts[1].x, this.mPts[1].y, this.mPts[2].x, this.mPts[2].y, this.mPts[3].x, this.mPts[3].y); */{} break; case kClose_Verb: /** * @j2sNative * ctx.closePath(); */{} break; default: break; } } // while Style sty = paint.getStyle(); if (Style.FILL.equals(sty)) { /** * @j2sNative * ctx.fill(); */{} } else if (Style.STROKE.equals(sty)) { /** * @j2sNative * ctx.stroke(); */{} } else if (Style.FILL_AND_STROKE.equals(sty)) { /** * @j2sNative * ctx.fill(); * ctx.stroke(); */{} } } private class PathIter { public static final int kAfterClose_NeedMoveToState = 0; public static final int kAfterCons_NeedMoveToState = 1; public static final int kAfterPrefix_NeedMoveToState = 2; private SkPath fPath; private int fPtsIndex; private int fVerbIndex; private int fVerbStop; private PointF fMoveTo; private PointF fLastPt; private boolean fForceClose; private boolean fNeedClose; private int fNeedMoveTo = kAfterPrefix_NeedMoveToState; private boolean fCloseLine; public PathIter(SkPath path, boolean forceClose) { this.setPath(path, forceClose); } private void setPath(SkPath path, boolean forceClose) { this.fPath = path; this.fPtsIndex = 0; this.fVerbIndex = 0; this.fVerbStop = path.fVerbs.size(); this.fForceClose = forceClose; this.fNeedClose = false; } public int next(PointF[] pts) { if (fVerbIndex == this.fVerbStop) { if (fNeedClose) { if (kLine_Verb == this.autoClose(pts)) { return kLine_Verb; } fNeedClose = false; return kDone_Verb; } return kDone_Verb; } int verb = fPath.fVerbs.get(fVerbIndex++); switch (verb) { case kMove_Verb: if (fNeedClose) { fVerbIndex -= 1; verb = this.autoClose(pts); if (verb == kClose_Verb) { fNeedClose = false; } return verb; } if (fVerbIndex == fVerbStop) { // might be a trailing moveto return kDone_Verb; } fMoveTo = fPath.fPts.get(fPtsIndex); if (pts != null) { pts[0] = fMoveTo; } fPtsIndex += 1; fNeedMoveTo = kAfterCons_NeedMoveToState; fNeedClose = fForceClose; break; case kLine_Verb: if (this.cons_moveTo(pts)) { return kMove_Verb; } if (pts != null) { pts[1] = fPath.fPts.get(fPtsIndex); } fLastPt = fPath.fPts.get(fPtsIndex); fCloseLine = false; fPtsIndex += 1; break; case kQuad_Verb: if (this.cons_moveTo(pts)) { return kMove_Verb; } if (pts != null) { pts[1] = fPath.fPts.get(fPtsIndex); pts[2] = fPath.fPts.get(fPtsIndex + 1); } fLastPt = fPath.fPts.get(fPtsIndex + 1); fPtsIndex += 2; break; case kCubic_Verb: if (this.cons_moveTo(pts)) { return kMove_Verb; } if (pts != null) { pts[1] = fPath.fPts.get(fPtsIndex); pts[2] = fPath.fPts.get(fPtsIndex + 1); pts[3] = fPath.fPts.get(fPtsIndex + 2); } fLastPt = fPath.fPts.get(fPtsIndex + 2);; fPtsIndex += 3; break; case kClose_Verb: verb = this.autoClose(pts); if (verb == kLine_Verb) { fVerbIndex -= 1; } else { fNeedClose = false; } fNeedMoveTo = kAfterClose_NeedMoveToState; break; } return verb; } private boolean cons_moveTo(PointF[] pts) { if (fNeedMoveTo == kAfterClose_NeedMoveToState) { if (pts != null) { pts[0] = fMoveTo; } fNeedClose = fForceClose; fNeedMoveTo = kAfterCons_NeedMoveToState; fVerbIndex -= 1; return true; } if (fNeedMoveTo == kAfterCons_NeedMoveToState) { if (pts != null) { pts[0] = fMoveTo; } fNeedMoveTo = kAfterPrefix_NeedMoveToState; } else { if (pts != null) { pts[0] = fPath.fPts.get(fPtsIndex - 1); } } return false; } private int autoClose(PointF[] pts) { if (fLastPt.x != fMoveTo.x || fLastPt.y != fMoveTo.y) { // A special case: if both points are NaN, SkPoint::operation== returns // false, but the iterator expects that they are treated as the same. // (consider SkPoint is a 2-dimension float point). if (Float.isNaN(fLastPt.x) || Float.isNaN(fLastPt.y) || Float.isNaN(fMoveTo.x) || Float.isNaN(fMoveTo.y)) { return kClose_Verb; } if (pts != null) { pts[0] = fLastPt; pts[1] = fMoveTo; } fLastPt = fMoveTo; fCloseLine = true; return kLine_Verb; } return kClose_Verb; } } // PathIter } // SkPath private SkPath init1() { return new SkPath(); } private SkPath init2(SkPath src) { return new SkPath(src); } private static void native_reset(SkPath path) { path.reset(); } private static void native_rewind(SkPath path) { path.rewind(); } private static void native_set(SkPath path, SkPath src) { path.set(src); } private static int native_getFillType(SkPath path) { return path.getFillType(); } private static void native_setFillType(SkPath path, int ft) { path.setFillType(ft); } private static boolean native_isEmpty(SkPath path) { return path.isEmpty(); } private static boolean native_isRect(SkPath path, RectF rect) { return path.isRect(rect); } private static void native_computeBounds(SkPath path, RectF bounds) { path.computeBounds(bounds); } private static native void native_incReserve(int nPath, int extraPtCount); private static void native_moveTo(SkPath path, float x, float y) { path.moveTo(x, y); } private static void native_rMoveTo(SkPath path, float dx, float dy) { path.rMoveTo(dx, dy); } private static void native_lineTo(SkPath path, float x, float y) { path.lineTo(x, y); } private static void native_rLineTo(SkPath path, float dx, float dy) { path.rLineTo(dx, dy); } private static void native_quadTo(SkPath path, float x1, float y1, float x2, float y2) { path.quadTo(x1, y1, x2, y2); } private static void native_rQuadTo(SkPath path, float dx1, float dy1, float dx2, float dy2) { path.rQuadTo(dx1, dy1, dx2, dy2); } private static void native_cubicTo(SkPath path, float x1, float y1, float x2, float y2, float x3, float y3) { path.cubicTo(x1, y1, x2, y2, x3, y3); } private static void native_rCubicTo(SkPath path, float x1, float y1, float x2, float y2, float x3, float y3) { path.rCubicTo(x1, y1, x2, y2, x3, y3); } private static void native_arcTo(SkPath path, RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) { path.arcTo(oval, startAngle, sweepAngle, forceMoveTo); } private static void native_close(SkPath path) { path.close(); } private static void native_addRect(SkPath path, RectF rect, int dir) { path.addRect(rect.left, rect.top, rect.right, rect.bottom, dir); } private static void native_addRect(SkPath path, float left, float top, float right, float bottom, int dir) { path.addRect(left, top, right, bottom, dir); } private static void native_addOval(SkPath path, RectF oval, int dir) { path.addOval(oval, dir); } private static void native_addCircle(SkPath path, float x, float y, float radius, int dir) { path.addCircle(x, y, radius, dir); } private static void native_addArc(SkPath path, RectF oval, float startAngle, float sweepAngle) { path.addArc(oval, startAngle, sweepAngle); } private static void native_addRoundRect(SkPath path, RectF rect, float rx, float ry, int dir) { path.addRoundRect(rect, rx, ry, dir); } private static void native_addRoundRect(SkPath path, RectF r, float[] radii, int dir) { path.addRoundRect(r, radii, dir); } private static void native_addPath(SkPath path, SkPath src, float dx, float dy) { path.addPath(src, dx, dy); } private static void native_addPath(SkPath path, SkPath src) { path.addPath(src); } private static void native_addPath(SkPath path, SkPath src, Matrix matrix) { path.addPath(src, matrix); } private static void native_offset(SkPath path, float dx, float dy, SkPath dst_path) { Matrix matrix = new Matrix(); matrix.setTranslate(dx, dy); path.transform(matrix, dst_path); } private static void native_offset(SkPath path, float dx, float dy) { native_offset(path, dx, dy, path); } private static void native_setLastPoint(SkPath path, float dx, float dy) { path.setLastPt(dx, dy); } private static void native_transform(SkPath path, Matrix matrix, SkPath dst_path) { path.transform(matrix, dst_path); } private static void native_transform(SkPath path, Matrix matrix) { path.transform(matrix, path); } private static void native_drawOnCanvas(SkPath path, String canvas, Bitmap bitmap, Paint paint) { path.drawOnCanvas(canvas, bitmap, paint); } private static native void finalizer(int nPath); private final SkPath mNativePath; }