1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.graphics;
18 
19 import android.annotation.FloatRange;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.Size;
23 import android.compat.annotation.UnsupportedAppUsage;
24 
25 import dalvik.annotation.optimization.CriticalNative;
26 import dalvik.annotation.optimization.FastNative;
27 
28 import libcore.util.NativeAllocationRegistry;
29 
30 /**
31  * The Path class encapsulates compound (multiple contour) geometric paths
32  * consisting of straight line segments, quadratic curves, and cubic curves.
33  * It can be drawn with canvas.drawPath(path, paint), either filled or stroked
34  * (based on the paint's Style), or it can be used for clipping or to draw
35  * text on a path.
36  */
37 public class Path {
38 
39     private static final NativeAllocationRegistry sRegistry =
40             NativeAllocationRegistry.createMalloced(
41                 Path.class.getClassLoader(), nGetFinalizer());
42 
43     /**
44      * @hide
45      */
46     public final long mNativePath;
47 
48     /**
49      * @hide
50      */
51     @UnsupportedAppUsage
52     public boolean isSimplePath = true;
53     /**
54      * @hide
55      */
56     @UnsupportedAppUsage
57     public Region rects;
58     private Direction mLastDirection = null;
59 
60     /**
61      * Create an empty path
62      */
Path()63     public Path() {
64         mNativePath = nInit();
65         sRegistry.registerNativeAllocation(this, mNativePath);
66     }
67 
68     /**
69      * Create a new path, copying the contents from the src path.
70      *
71      * @param src The path to copy from when initializing the new path
72      */
Path(@ullable Path src)73     public Path(@Nullable Path src) {
74         long valNative = 0;
75         if (src != null) {
76             valNative = src.mNativePath;
77             isSimplePath = src.isSimplePath;
78             if (src.rects != null) {
79                 rects = new Region(src.rects);
80             }
81         }
82         mNativePath = nInit(valNative);
83         sRegistry.registerNativeAllocation(this, mNativePath);
84     }
85 
86     /**
87      * Clear any lines and curves from the path, making it empty.
88      * This does NOT change the fill-type setting.
89      */
reset()90     public void reset() {
91         isSimplePath = true;
92         mLastDirection = null;
93         if (rects != null) rects.setEmpty();
94         // We promised not to change this, so preserve it around the native
95         // call, which does now reset fill type.
96         final FillType fillType = getFillType();
97         nReset(mNativePath);
98         setFillType(fillType);
99     }
100 
101     /**
102      * Rewinds the path: clears any lines and curves from the path but
103      * keeps the internal data structure for faster reuse.
104      */
rewind()105     public void rewind() {
106         isSimplePath = true;
107         mLastDirection = null;
108         if (rects != null) rects.setEmpty();
109         nRewind(mNativePath);
110     }
111 
112     /** Replace the contents of this with the contents of src.
113     */
set(@onNull Path src)114     public void set(@NonNull Path src) {
115         if (this == src) {
116             return;
117         }
118         isSimplePath = src.isSimplePath;
119         nSet(mNativePath, src.mNativePath);
120         if (!isSimplePath) {
121             return;
122         }
123 
124         if (rects != null && src.rects != null) {
125             rects.set(src.rects);
126         } else if (rects != null && src.rects == null) {
127             rects.setEmpty();
128         } else if (src.rects != null) {
129             rects = new Region(src.rects);
130         }
131     }
132 
133     /**
134      * The logical operations that can be performed when combining two paths.
135      *
136      * @see #op(Path, android.graphics.Path.Op)
137      * @see #op(Path, Path, android.graphics.Path.Op)
138      */
139     public enum Op {
140         /**
141          * Subtract the second path from the first path.
142          */
143         DIFFERENCE,
144         /**
145          * Intersect the two paths.
146          */
147         INTERSECT,
148         /**
149          * Union (inclusive-or) the two paths.
150          */
151         UNION,
152         /**
153          * Exclusive-or the two paths.
154          */
155         XOR,
156         /**
157          * Subtract the first path from the second path.
158          */
159         REVERSE_DIFFERENCE
160     }
161 
162     /**
163      * Set this path to the result of applying the Op to this path and the specified path.
164      * The resulting path will be constructed from non-overlapping contours.
165      * The curve order is reduced where possible so that cubics may be turned
166      * into quadratics, and quadratics maybe turned into lines.
167      *
168      * @param path The second operand (for difference, the subtrahend)
169      *
170      * @return True if operation succeeded, false otherwise and this path remains unmodified.
171      *
172      * @see Op
173      * @see #op(Path, Path, android.graphics.Path.Op)
174      */
op(@onNull Path path, @NonNull Op op)175     public boolean op(@NonNull Path path, @NonNull Op op) {
176         return op(this, path, op);
177     }
178 
179     /**
180      * Set this path to the result of applying the Op to the two specified paths.
181      * The resulting path will be constructed from non-overlapping contours.
182      * The curve order is reduced where possible so that cubics may be turned
183      * into quadratics, and quadratics maybe turned into lines.
184      *
185      * @param path1 The first operand (for difference, the minuend)
186      * @param path2 The second operand (for difference, the subtrahend)
187      *
188      * @return True if operation succeeded, false otherwise and this path remains unmodified.
189      *
190      * @see Op
191      * @see #op(Path, android.graphics.Path.Op)
192      */
op(@onNull Path path1, @NonNull Path path2, @NonNull Op op)193     public boolean op(@NonNull Path path1, @NonNull Path path2, @NonNull Op op) {
194         if (nOp(path1.mNativePath, path2.mNativePath, op.ordinal(), this.mNativePath)) {
195             isSimplePath = false;
196             rects = null;
197             return true;
198         }
199         return false;
200     }
201 
202     /**
203      * Returns the path's convexity, as defined by the content of the path.
204      * <p>
205      * A path is convex if it has a single contour, and only ever curves in a
206      * single direction.
207      * <p>
208      * This function will calculate the convexity of the path from its control
209      * points, and cache the result.
210      *
211      * @return True if the path is convex.
212      *
213      * @deprecated This method is not reliable. The way convexity is computed may change from
214      * release to release, and convexity could change based on a matrix as well. This method was
215      * useful when non-convex Paths were unable to be used in certain contexts, but that is no
216      * longer the case.
217      */
218     @Deprecated
isConvex()219     public boolean isConvex() {
220         return nIsConvex(mNativePath);
221     }
222 
223     /**
224      * Enum for the ways a path may be filled.
225      */
226     public enum FillType {
227         // these must match the values in SkPath.h
228         /**
229          * Specifies that "inside" is computed by a non-zero sum of signed
230          * edge crossings.
231          */
232         WINDING         (0),
233         /**
234          * Specifies that "inside" is computed by an odd number of edge
235          * crossings.
236          */
237         EVEN_ODD        (1),
238         /**
239          * Same as {@link #WINDING}, but draws outside of the path, rather than inside.
240          */
241         INVERSE_WINDING (2),
242         /**
243          * Same as {@link #EVEN_ODD}, but draws outside of the path, rather than inside.
244          */
245         INVERSE_EVEN_ODD(3);
246 
FillType(int ni)247         FillType(int ni) {
248             nativeInt = ni;
249         }
250 
251         final int nativeInt;
252     }
253 
254     // these must be in the same order as their native values
255     static final FillType[] sFillTypeArray = {
256         FillType.WINDING,
257         FillType.EVEN_ODD,
258         FillType.INVERSE_WINDING,
259         FillType.INVERSE_EVEN_ODD
260     };
261 
262     /**
263      * Return the path's fill type. This defines how "inside" is
264      * computed. The default value is WINDING.
265      *
266      * @return the path's fill type
267      */
268     @NonNull
getFillType()269     public FillType getFillType() {
270         return sFillTypeArray[nGetFillType(mNativePath)];
271     }
272 
273     /**
274      * Set the path's fill type. This defines how "inside" is computed.
275      *
276      * @param ft The new fill type for this path
277      */
setFillType(@onNull FillType ft)278     public void setFillType(@NonNull FillType ft) {
279         nSetFillType(mNativePath, ft.nativeInt);
280     }
281 
282     /**
283      * Returns true if the filltype is one of the INVERSE variants
284      *
285      * @return true if the filltype is one of the INVERSE variants
286      */
isInverseFillType()287     public boolean isInverseFillType() {
288         final int ft = nGetFillType(mNativePath);
289         return (ft & FillType.INVERSE_WINDING.nativeInt) != 0;
290     }
291 
292     /**
293      * Toggles the INVERSE state of the filltype
294      */
toggleInverseFillType()295     public void toggleInverseFillType() {
296         int ft = nGetFillType(mNativePath);
297         ft ^= FillType.INVERSE_WINDING.nativeInt;
298         nSetFillType(mNativePath, ft);
299     }
300 
301     /**
302      * Returns true if the path is empty (contains no lines or curves)
303      *
304      * @return true if the path is empty (contains no lines or curves)
305      */
isEmpty()306     public boolean isEmpty() {
307         return nIsEmpty(mNativePath);
308     }
309 
310     /**
311      * Returns true if the path specifies a rectangle. If so, and if rect is
312      * not null, set rect to the bounds of the path. If the path does not
313      * specify a rectangle, return false and ignore rect.
314      *
315      * @param rect If not null, returns the bounds of the path if it specifies
316      *             a rectangle
317      * @return     true if the path specifies a rectangle
318      */
isRect(@ullable RectF rect)319     public boolean isRect(@Nullable  RectF rect) {
320         return nIsRect(mNativePath, rect);
321     }
322 
323     /**
324      * Compute the bounds of the control points of the path, and write the
325      * answer into bounds. If the path contains 0 or 1 points, the bounds is
326      * set to (0,0,0,0)
327      *
328      * @param bounds Returns the computed bounds of the path's control points.
329      * @param exact This parameter is no longer used.
330      */
331     @SuppressWarnings({"UnusedDeclaration"})
computeBounds(@onNull RectF bounds, boolean exact)332     public void computeBounds(@NonNull RectF bounds, boolean exact) {
333         nComputeBounds(mNativePath, bounds);
334     }
335 
336     /**
337      * Hint to the path to prepare for adding more points. This can allow the
338      * path to more efficiently allocate its storage.
339      *
340      * @param extraPtCount The number of extra points that may be added to this
341      *                     path
342      */
incReserve(int extraPtCount)343     public void incReserve(int extraPtCount) {
344         nIncReserve(mNativePath, extraPtCount);
345     }
346 
347     /**
348      * Set the beginning of the next contour to the point (x,y).
349      *
350      * @param x The x-coordinate of the start of a new contour
351      * @param y The y-coordinate of the start of a new contour
352      */
moveTo(float x, float y)353     public void moveTo(float x, float y) {
354         nMoveTo(mNativePath, x, y);
355     }
356 
357     /**
358      * Set the beginning of the next contour relative to the last point on the
359      * previous contour. If there is no previous contour, this is treated the
360      * same as moveTo().
361      *
362      * @param dx The amount to add to the x-coordinate of the end of the
363      *           previous contour, to specify the start of a new contour
364      * @param dy The amount to add to the y-coordinate of the end of the
365      *           previous contour, to specify the start of a new contour
366      */
rMoveTo(float dx, float dy)367     public void rMoveTo(float dx, float dy) {
368         nRMoveTo(mNativePath, dx, dy);
369     }
370 
371     /**
372      * Add a line from the last point to the specified point (x,y).
373      * If no moveTo() call has been made for this contour, the first point is
374      * automatically set to (0,0).
375      *
376      * @param x The x-coordinate of the end of a line
377      * @param y The y-coordinate of the end of a line
378      */
lineTo(float x, float y)379     public void lineTo(float x, float y) {
380         isSimplePath = false;
381         nLineTo(mNativePath, x, y);
382     }
383 
384     /**
385      * Same as lineTo, but the coordinates are considered relative to the last
386      * point on this contour. If there is no previous point, then a moveTo(0,0)
387      * is inserted automatically.
388      *
389      * @param dx The amount to add to the x-coordinate of the previous point on
390      *           this contour, to specify a line
391      * @param dy The amount to add to the y-coordinate of the previous point on
392      *           this contour, to specify a line
393      */
rLineTo(float dx, float dy)394     public void rLineTo(float dx, float dy) {
395         isSimplePath = false;
396         nRLineTo(mNativePath, dx, dy);
397     }
398 
399     /**
400      * Add a quadratic bezier from the last point, approaching control point
401      * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
402      * this contour, the first point is automatically set to (0,0).
403      *
404      * @param x1 The x-coordinate of the control point on a quadratic curve
405      * @param y1 The y-coordinate of the control point on a quadratic curve
406      * @param x2 The x-coordinate of the end point on a quadratic curve
407      * @param y2 The y-coordinate of the end point on a quadratic curve
408      */
quadTo(float x1, float y1, float x2, float y2)409     public void quadTo(float x1, float y1, float x2, float y2) {
410         isSimplePath = false;
411         nQuadTo(mNativePath, x1, y1, x2, y2);
412     }
413 
414     /**
415      * Same as quadTo, but the coordinates are considered relative to the last
416      * point on this contour. If there is no previous point, then a moveTo(0,0)
417      * is inserted automatically.
418      *
419      * @param dx1 The amount to add to the x-coordinate of the last point on
420      *            this contour, for the control point of a quadratic curve
421      * @param dy1 The amount to add to the y-coordinate of the last point on
422      *            this contour, for the control point of a quadratic curve
423      * @param dx2 The amount to add to the x-coordinate of the last point on
424      *            this contour, for the end point of a quadratic curve
425      * @param dy2 The amount to add to the y-coordinate of the last point on
426      *            this contour, for the end point of a quadratic curve
427      */
rQuadTo(float dx1, float dy1, float dx2, float dy2)428     public void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
429         isSimplePath = false;
430         nRQuadTo(mNativePath, dx1, dy1, dx2, dy2);
431     }
432 
433     /**
434      * Add a cubic bezier from the last point, approaching control points
435      * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
436      * made for this contour, the first point is automatically set to (0,0).
437      *
438      * @param x1 The x-coordinate of the 1st control point on a cubic curve
439      * @param y1 The y-coordinate of the 1st control point on a cubic curve
440      * @param x2 The x-coordinate of the 2nd control point on a cubic curve
441      * @param y2 The y-coordinate of the 2nd control point on a cubic curve
442      * @param x3 The x-coordinate of the end point on a cubic curve
443      * @param y3 The y-coordinate of the end point on a cubic curve
444      */
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)445     public void cubicTo(float x1, float y1, float x2, float y2,
446                         float x3, float y3) {
447         isSimplePath = false;
448         nCubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
449     }
450 
451     /**
452      * Same as cubicTo, but the coordinates are considered relative to the
453      * current point on this contour. If there is no previous point, then a
454      * moveTo(0,0) is inserted automatically.
455      */
rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)456     public void rCubicTo(float x1, float y1, float x2, float y2,
457                          float x3, float y3) {
458         isSimplePath = false;
459         nRCubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
460     }
461 
462     /**
463      * Append the specified arc to the path as a new contour. If the start of
464      * the path is different from the path's current last point, then an
465      * automatic lineTo() is added to connect the current contour to the
466      * start of the arc. However, if the path is empty, then we call moveTo()
467      * with the first point of the arc.
468      *
469      * @param oval        The bounds of oval defining shape and size of the arc
470      * @param startAngle  Starting angle (in degrees) where the arc begins
471      * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
472      *                    mod 360.
473      * @param forceMoveTo If true, always begin a new contour with the arc
474      */
arcTo(@onNull RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)475     public void arcTo(@NonNull RectF oval, float startAngle, float sweepAngle,
476                       boolean forceMoveTo) {
477         arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, forceMoveTo);
478     }
479 
480     /**
481      * Append the specified arc to the path as a new contour. If the start of
482      * the path is different from the path's current last point, then an
483      * automatic lineTo() is added to connect the current contour to the
484      * start of the arc. However, if the path is empty, then we call moveTo()
485      * with the first point of the arc.
486      *
487      * @param oval        The bounds of oval defining shape and size of the arc
488      * @param startAngle  Starting angle (in degrees) where the arc begins
489      * @param sweepAngle  Sweep angle (in degrees) measured clockwise
490      */
arcTo(@onNull RectF oval, float startAngle, float sweepAngle)491     public void arcTo(@NonNull RectF oval, float startAngle, float sweepAngle) {
492         arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, false);
493     }
494 
495     /**
496      * Append the specified arc to the path as a new contour. If the start of
497      * the path is different from the path's current last point, then an
498      * automatic lineTo() is added to connect the current contour to the
499      * start of the arc. However, if the path is empty, then we call moveTo()
500      * with the first point of the arc.
501      *
502      * @param startAngle  Starting angle (in degrees) where the arc begins
503      * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
504      *                    mod 360.
505      * @param forceMoveTo If true, always begin a new contour with the arc
506      */
arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)507     public void arcTo(float left, float top, float right, float bottom, float startAngle,
508             float sweepAngle, boolean forceMoveTo) {
509         isSimplePath = false;
510         nArcTo(mNativePath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo);
511     }
512 
513     /**
514      * Close the current contour. If the current point is not equal to the
515      * first point of the contour, a line segment is automatically added.
516      */
close()517     public void close() {
518         isSimplePath = false;
519         nClose(mNativePath);
520     }
521 
522     /**
523      * Specifies how closed shapes (e.g. rects, ovals) are oriented when they
524      * are added to a path.
525      */
526     public enum Direction {
527         /** clockwise */
528         CW  (0),    // must match enum in SkPath.h
529         /** counter-clockwise */
530         CCW (1);    // must match enum in SkPath.h
531 
Direction(int ni)532         Direction(int ni) {
533             nativeInt = ni;
534         }
535         final int nativeInt;
536     }
537 
detectSimplePath(float left, float top, float right, float bottom, Direction dir)538     private void detectSimplePath(float left, float top, float right, float bottom, Direction dir) {
539         if (mLastDirection == null) {
540             mLastDirection = dir;
541         }
542         if (mLastDirection != dir) {
543             isSimplePath = false;
544         } else {
545             if (rects == null) rects = new Region();
546             rects.op((int) left, (int) top, (int) right, (int) bottom, Region.Op.UNION);
547         }
548     }
549 
550     /**
551      * Add a closed rectangle contour to the path
552      *
553      * @param rect The rectangle to add as a closed contour to the path
554      * @param dir  The direction to wind the rectangle's contour
555      */
addRect(@onNull RectF rect, @NonNull Direction dir)556     public void addRect(@NonNull RectF rect, @NonNull Direction dir) {
557         addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
558     }
559 
560     /**
561      * Add a closed rectangle contour to the path
562      *
563      * @param left   The left side of a rectangle to add to the path
564      * @param top    The top of a rectangle to add to the path
565      * @param right  The right side of a rectangle to add to the path
566      * @param bottom The bottom of a rectangle to add to the path
567      * @param dir    The direction to wind the rectangle's contour
568      */
addRect(float left, float top, float right, float bottom, @NonNull Direction dir)569     public void addRect(float left, float top, float right, float bottom, @NonNull Direction dir) {
570         detectSimplePath(left, top, right, bottom, dir);
571         nAddRect(mNativePath, left, top, right, bottom, dir.nativeInt);
572     }
573 
574     /**
575      * Add a closed oval contour to the path
576      *
577      * @param oval The bounds of the oval to add as a closed contour to the path
578      * @param dir  The direction to wind the oval's contour
579      */
addOval(@onNull RectF oval, @NonNull Direction dir)580     public void addOval(@NonNull RectF oval, @NonNull Direction dir) {
581         addOval(oval.left, oval.top, oval.right, oval.bottom, dir);
582     }
583 
584     /**
585      * Add a closed oval contour to the path
586      *
587      * @param dir The direction to wind the oval's contour
588      */
addOval(float left, float top, float right, float bottom, @NonNull Direction dir)589     public void addOval(float left, float top, float right, float bottom, @NonNull Direction dir) {
590         isSimplePath = false;
591         nAddOval(mNativePath, left, top, right, bottom, dir.nativeInt);
592     }
593 
594     /**
595      * Add a closed circle contour to the path
596      *
597      * @param x   The x-coordinate of the center of a circle to add to the path
598      * @param y   The y-coordinate of the center of a circle to add to the path
599      * @param radius The radius of a circle to add to the path
600      * @param dir    The direction to wind the circle's contour
601      */
addCircle(float x, float y, float radius, @NonNull Direction dir)602     public void addCircle(float x, float y, float radius, @NonNull Direction dir) {
603         isSimplePath = false;
604         nAddCircle(mNativePath, x, y, radius, dir.nativeInt);
605     }
606 
607     /**
608      * Add the specified arc to the path as a new contour.
609      *
610      * @param oval The bounds of oval defining the shape and size of the arc
611      * @param startAngle Starting angle (in degrees) where the arc begins
612      * @param sweepAngle Sweep angle (in degrees) measured clockwise
613      */
addArc(@onNull RectF oval, float startAngle, float sweepAngle)614     public void addArc(@NonNull RectF oval, float startAngle, float sweepAngle) {
615         addArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle);
616     }
617 
618     /**
619      * Add the specified arc to the path as a new contour.
620      *
621      * @param startAngle Starting angle (in degrees) where the arc begins
622      * @param sweepAngle Sweep angle (in degrees) measured clockwise
623      */
addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle)624     public void addArc(float left, float top, float right, float bottom, float startAngle,
625             float sweepAngle) {
626         isSimplePath = false;
627         nAddArc(mNativePath, left, top, right, bottom, startAngle, sweepAngle);
628     }
629 
630     /**
631         * Add a closed round-rectangle contour to the path
632      *
633      * @param rect The bounds of a round-rectangle to add to the path
634      * @param rx   The x-radius of the rounded corners on the round-rectangle
635      * @param ry   The y-radius of the rounded corners on the round-rectangle
636      * @param dir  The direction to wind the round-rectangle's contour
637      */
addRoundRect(@onNull RectF rect, float rx, float ry, @NonNull Direction dir)638     public void addRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Direction dir) {
639         addRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, dir);
640     }
641 
642     /**
643      * Add a closed round-rectangle contour to the path
644      *
645      * @param rx   The x-radius of the rounded corners on the round-rectangle
646      * @param ry   The y-radius of the rounded corners on the round-rectangle
647      * @param dir  The direction to wind the round-rectangle's contour
648      */
addRoundRect(float left, float top, float right, float bottom, float rx, float ry, @NonNull Direction dir)649     public void addRoundRect(float left, float top, float right, float bottom, float rx, float ry,
650             @NonNull Direction dir) {
651         isSimplePath = false;
652         nAddRoundRect(mNativePath, left, top, right, bottom, rx, ry, dir.nativeInt);
653     }
654 
655     /**
656      * Add a closed round-rectangle contour to the path. Each corner receives
657      * two radius values [X, Y]. The corners are ordered top-left, top-right,
658      * bottom-right, bottom-left
659      *
660      * @param rect The bounds of a round-rectangle to add to the path
661      * @param radii Array of 8 values, 4 pairs of [X,Y] radii
662      * @param dir  The direction to wind the round-rectangle's contour
663      */
addRoundRect(@onNull RectF rect, @NonNull float[] radii, @NonNull Direction dir)664     public void addRoundRect(@NonNull RectF rect, @NonNull float[] radii, @NonNull Direction dir) {
665         if (rect == null) {
666             throw new NullPointerException("need rect parameter");
667         }
668         addRoundRect(rect.left, rect.top, rect.right, rect.bottom, radii, dir);
669     }
670 
671     /**
672      * Add a closed round-rectangle contour to the path. Each corner receives
673      * two radius values [X, Y]. The corners are ordered top-left, top-right,
674      * bottom-right, bottom-left
675      *
676      * @param radii Array of 8 values, 4 pairs of [X,Y] radii
677      * @param dir  The direction to wind the round-rectangle's contour
678      */
addRoundRect(float left, float top, float right, float bottom, @NonNull float[] radii, @NonNull Direction dir)679     public void addRoundRect(float left, float top, float right, float bottom,
680             @NonNull float[] radii, @NonNull Direction dir) {
681         if (radii.length < 8) {
682             throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
683         }
684         isSimplePath = false;
685         nAddRoundRect(mNativePath, left, top, right, bottom, radii, dir.nativeInt);
686     }
687 
688     /**
689      * Add a copy of src to the path, offset by (dx,dy)
690      *
691      * @param src The path to add as a new contour
692      * @param dx  The amount to translate the path in X as it is added
693      */
addPath(@onNull Path src, float dx, float dy)694     public void addPath(@NonNull Path src, float dx, float dy) {
695         isSimplePath = false;
696         nAddPath(mNativePath, src.mNativePath, dx, dy);
697     }
698 
699     /**
700      * Add a copy of src to the path
701      *
702      * @param src The path that is appended to the current path
703      */
addPath(@onNull Path src)704     public void addPath(@NonNull Path src) {
705         isSimplePath = false;
706         nAddPath(mNativePath, src.mNativePath);
707     }
708 
709     /**
710      * Add a copy of src to the path, transformed by matrix
711      *
712      * @param src The path to add as a new contour
713      */
addPath(@onNull Path src, @NonNull Matrix matrix)714     public void addPath(@NonNull Path src, @NonNull Matrix matrix) {
715         if (!src.isSimplePath) isSimplePath = false;
716         nAddPath(mNativePath, src.mNativePath, matrix.native_instance);
717     }
718 
719     /**
720      * Offset the path by (dx,dy)
721      *
722      * @param dx  The amount in the X direction to offset the entire path
723      * @param dy  The amount in the Y direction to offset the entire path
724      * @param dst The translated path is written here. If this is null, then
725      *            the original path is modified.
726      */
offset(float dx, float dy, @Nullable Path dst)727     public void offset(float dx, float dy, @Nullable Path dst) {
728         if (dst != null) {
729             dst.set(this);
730         } else {
731             dst = this;
732         }
733         dst.offset(dx, dy);
734     }
735 
736     /**
737      * Offset the path by (dx,dy)
738      *
739      * @param dx The amount in the X direction to offset the entire path
740      * @param dy The amount in the Y direction to offset the entire path
741      */
offset(float dx, float dy)742     public void offset(float dx, float dy) {
743         if (isSimplePath && rects == null) {
744             // nothing to offset
745             return;
746         }
747         if (isSimplePath && dx == Math.rint(dx) && dy == Math.rint(dy)) {
748             rects.translate((int) dx, (int) dy);
749         } else {
750             isSimplePath = false;
751         }
752         nOffset(mNativePath, dx, dy);
753     }
754 
755     /**
756      * Sets the last point of the path.
757      *
758      * @param dx The new X coordinate for the last point
759      * @param dy The new Y coordinate for the last point
760      */
setLastPoint(float dx, float dy)761     public void setLastPoint(float dx, float dy) {
762         isSimplePath = false;
763         nSetLastPoint(mNativePath, dx, dy);
764     }
765 
766     /**
767      * Transform the points in this path by matrix, and write the answer
768      * into dst. If dst is null, then the the original path is modified.
769      *
770      * @param matrix The matrix to apply to the path
771      * @param dst    The transformed path is written here. If dst is null,
772      *               then the the original path is modified
773      */
transform(@onNull Matrix matrix, @Nullable Path dst)774     public void transform(@NonNull Matrix matrix, @Nullable Path dst) {
775         long dstNative = 0;
776         if (dst != null) {
777             dst.isSimplePath = false;
778             dstNative = dst.mNativePath;
779         }
780         nTransform(mNativePath, matrix.native_instance, dstNative);
781     }
782 
783     /**
784      * Transform the points in this path by matrix.
785      *
786      * @param matrix The matrix to apply to the path
787      */
transform(@onNull Matrix matrix)788     public void transform(@NonNull Matrix matrix) {
789         isSimplePath = false;
790         nTransform(mNativePath, matrix.native_instance);
791     }
792 
793     /** @hide */
readOnlyNI()794     public final long readOnlyNI() {
795         return mNativePath;
796     }
797 
mutateNI()798     final long mutateNI() {
799         isSimplePath = false;
800         return mNativePath;
801     }
802 
803     /**
804      * Approximate the <code>Path</code> with a series of line segments.
805      * This returns float[] with the array containing point components.
806      * There are three components for each point, in order:
807      * <ul>
808      *     <li>Fraction along the length of the path that the point resides</li>
809      *     <li>The x coordinate of the point</li>
810      *     <li>The y coordinate of the point</li>
811      * </ul>
812      * <p>Two points may share the same fraction along its length when there is
813      * a move action within the Path.</p>
814      *
815      * @param acceptableError The acceptable error for a line on the
816      *                        Path. Typically this would be 0.5 so that
817      *                        the error is less than half a pixel.
818      * @return An array of components for points approximating the Path.
819      */
820     @NonNull
821     @Size(min = 6, multiple = 3)
approximate(@loatRangefrom = 0) float acceptableError)822     public float[] approximate(@FloatRange(from = 0) float acceptableError) {
823         if (acceptableError < 0) {
824             throw new IllegalArgumentException("AcceptableError must be greater than or equal to 0");
825         }
826         return nApproximate(mNativePath, acceptableError);
827     }
828 
829     // ------------------ Regular JNI ------------------------
830 
nInit()831     private static native long nInit();
nInit(long nPath)832     private static native long nInit(long nPath);
nGetFinalizer()833     private static native long nGetFinalizer();
nSet(long native_dst, long nSrc)834     private static native void nSet(long native_dst, long nSrc);
nComputeBounds(long nPath, RectF bounds)835     private static native void nComputeBounds(long nPath, RectF bounds);
nIncReserve(long nPath, int extraPtCount)836     private static native void nIncReserve(long nPath, int extraPtCount);
nMoveTo(long nPath, float x, float y)837     private static native void nMoveTo(long nPath, float x, float y);
nRMoveTo(long nPath, float dx, float dy)838     private static native void nRMoveTo(long nPath, float dx, float dy);
nLineTo(long nPath, float x, float y)839     private static native void nLineTo(long nPath, float x, float y);
nRLineTo(long nPath, float dx, float dy)840     private static native void nRLineTo(long nPath, float dx, float dy);
nQuadTo(long nPath, float x1, float y1, float x2, float y2)841     private static native void nQuadTo(long nPath, float x1, float y1, float x2, float y2);
nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2)842     private static native void nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2);
nCubicTo(long nPath, float x1, float y1, float x2, float y2, float x3, float y3)843     private static native void nCubicTo(long nPath, float x1, float y1, float x2, float y2,
844             float x3, float y3);
nRCubicTo(long nPath, float x1, float y1, float x2, float y2, float x3, float y3)845     private static native void nRCubicTo(long nPath, float x1, float y1, float x2, float y2,
846             float x3, float y3);
nArcTo(long nPath, float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo)847     private static native void nArcTo(long nPath, float left, float top, float right, float bottom,
848             float startAngle, float sweepAngle, boolean forceMoveTo);
nClose(long nPath)849     private static native void nClose(long nPath);
nAddRect(long nPath, float left, float top, float right, float bottom, int dir)850     private static native void nAddRect(long nPath, float left, float top,
851             float right, float bottom, int dir);
nAddOval(long nPath, float left, float top, float right, float bottom, int dir)852     private static native void nAddOval(long nPath, float left, float top,
853             float right, float bottom, int dir);
nAddCircle(long nPath, float x, float y, float radius, int dir)854     private static native void nAddCircle(long nPath, float x, float y, float radius, int dir);
nAddArc(long nPath, float left, float top, float right, float bottom, float startAngle, float sweepAngle)855     private static native void nAddArc(long nPath, float left, float top, float right, float bottom,
856             float startAngle, float sweepAngle);
nAddRoundRect(long nPath, float left, float top, float right, float bottom, float rx, float ry, int dir)857     private static native void nAddRoundRect(long nPath, float left, float top,
858             float right, float bottom, float rx, float ry, int dir);
nAddRoundRect(long nPath, float left, float top, float right, float bottom, float[] radii, int dir)859     private static native void nAddRoundRect(long nPath, float left, float top,
860             float right, float bottom, float[] radii, int dir);
nAddPath(long nPath, long src, float dx, float dy)861     private static native void nAddPath(long nPath, long src, float dx, float dy);
nAddPath(long nPath, long src)862     private static native void nAddPath(long nPath, long src);
nAddPath(long nPath, long src, long matrix)863     private static native void nAddPath(long nPath, long src, long matrix);
nOffset(long nPath, float dx, float dy)864     private static native void nOffset(long nPath, float dx, float dy);
nSetLastPoint(long nPath, float dx, float dy)865     private static native void nSetLastPoint(long nPath, float dx, float dy);
nTransform(long nPath, long matrix, long dst_path)866     private static native void nTransform(long nPath, long matrix, long dst_path);
nTransform(long nPath, long matrix)867     private static native void nTransform(long nPath, long matrix);
nOp(long path1, long path2, int op, long result)868     private static native boolean nOp(long path1, long path2, int op, long result);
nApproximate(long nPath, float error)869     private static native float[] nApproximate(long nPath, float error);
870 
871     // ------------------ Fast JNI ------------------------
872 
873     @FastNative
nIsRect(long nPath, RectF rect)874     private static native boolean nIsRect(long nPath, RectF rect);
875 
876     // ------------------ Critical JNI ------------------------
877 
878     @CriticalNative
nReset(long nPath)879     private static native void nReset(long nPath);
880     @CriticalNative
nRewind(long nPath)881     private static native void nRewind(long nPath);
882     @CriticalNative
nIsEmpty(long nPath)883     private static native boolean nIsEmpty(long nPath);
884     @CriticalNative
nIsConvex(long nPath)885     private static native boolean nIsConvex(long nPath);
886     @CriticalNative
nGetFillType(long nPath)887     private static native int nGetFillType(long nPath);
888     @CriticalNative
nSetFillType(long nPath, int ft)889     private static native void nSetFillType(long nPath, int ft);
890 }
891