Add APIs to make Android Path queryable and more useful
The underlying Skia implementation of Path has APIs for
querying the data in a path (via SkPath::Iter). It also exposes
the ability to add conic sections (conicTo()), which is useful
since a Path can contain conics (even if it was not created with
conics directly). And it adds a method to get the generationId
from a Path, used to see whether a Path changed since the
last time that Id was retrieved. Finally, it exposes a simple
interpolate() function to allow linear interpolation between
two paths.
This CL adds PathIterator to enable querying Path data, and
adds conicTo, isInterpolatable(), and interpolate() to Path,
all of which wrap the underlying, existing Skia functionality.
Bug: 157391114
Bug: 223586753
Test: Added PathIteratorTest plus new tests on PathTest
Change-Id: Iddadfde24dd75ac04d0a2f3ebca767613af25360
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/PathIteratorPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/PathIteratorPerfTest.java
new file mode 100644
index 0000000..6b739bc
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/PathIteratorPerfTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 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.perftests;
+
+import static android.graphics.PathIterator.VERB_DONE;
+
+import android.graphics.Path;
+import android.graphics.PathIterator;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.filters.LargeTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class PathIteratorPerfTest {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ private Path constructCircularPath(int numSegments) {
+ Path path = new Path();
+ float angleIncrement = (float) (2 * Math.PI / numSegments);
+ float radius = 200f;
+ float prevX = 0f, prevY = 0f;
+ float angle = 0f;
+ for (int i = 0; i <= numSegments; ++i) {
+ float x = (float) Math.cos(angle) * radius;
+ float y = (float) Math.sin(angle) * radius;
+ if (i > 0) {
+ path.cubicTo(prevX, prevY, x, y, x, y);
+ } else {
+ path.moveTo(x, y);
+ }
+ prevX = x;
+ prevY = y;
+ angle += angleIncrement;
+ }
+ return path;
+ }
+
+ private void testNextSegmentImpl(int numSegments) {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ Path path = constructCircularPath(numSegments);
+ while (state.keepRunning()) {
+ PathIterator iterator = path.iterator();
+ PathIterator.Segment segment = iterator.next();
+ while (segment.getVerb() != VERB_DONE) {
+ segment = iterator.next();
+ }
+ }
+ }
+
+ private void testNextFloatsImpl(int numSegments) {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ float[] points = new float[8];
+ Path path = constructCircularPath(numSegments);
+ while (state.keepRunning()) {
+ PathIterator iterator = path.iterator();
+ int verb = iterator.next(points, 0);
+ while (verb != VERB_DONE) {
+ verb = iterator.next(points, 0);
+ }
+ }
+ }
+
+ @Test
+ public void testNextSegment10() {
+ testNextSegmentImpl(10);
+ }
+
+ @Test
+ public void testNextSegment100() {
+ testNextSegmentImpl(100);
+ }
+
+ @Test
+ public void testNextSegment1000() {
+ testNextSegmentImpl(1000);
+ }
+
+ @Test
+ public void testNextArray10() {
+ testNextFloatsImpl(10);
+ }
+
+ @Test
+ public void testNextArray100() {
+ testNextFloatsImpl(100);
+ }
+
+ @Test
+ public void testNextArray1000() {
+ testNextFloatsImpl(1000);
+ }
+
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 7ed1a43..ba61ca1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -15111,7 +15111,7 @@
field @NonNull public static final android.os.Parcelable.Creator<android.graphics.ParcelableColorSpace> CREATOR;
}
- public class Path {
+ public class Path implements java.lang.Iterable<android.graphics.PathIterator.Segment> {
ctor public Path();
ctor public Path(@Nullable android.graphics.Path);
method public void addArc(@NonNull android.graphics.RectF, float, float);
@@ -15134,13 +15134,18 @@
method public void arcTo(float, float, float, float, float, float, boolean);
method public void close();
method public void computeBounds(@NonNull android.graphics.RectF, boolean);
+ method public void conicTo(float, float, float, float, float);
method public void cubicTo(float, float, float, float, float, float);
method @NonNull public android.graphics.Path.FillType getFillType();
+ method public int getGenerationId();
method public void incReserve(int);
+ method public boolean interpolate(@NonNull android.graphics.Path, float, @NonNull android.graphics.Path);
method @Deprecated public boolean isConvex();
method public boolean isEmpty();
+ method public boolean isInterpolatable(@NonNull android.graphics.Path);
method public boolean isInverseFillType();
method public boolean isRect(@Nullable android.graphics.RectF);
+ method @NonNull public android.graphics.PathIterator iterator();
method public void lineTo(float, float);
method public void moveTo(float, float);
method public void offset(float, float, @Nullable android.graphics.Path);
@@ -15148,6 +15153,7 @@
method public boolean op(@NonNull android.graphics.Path, @NonNull android.graphics.Path.Op);
method public boolean op(@NonNull android.graphics.Path, @NonNull android.graphics.Path, @NonNull android.graphics.Path.Op);
method public void quadTo(float, float, float, float);
+ method public void rConicTo(float, float, float, float, float);
method public void rCubicTo(float, float, float, float, float, float);
method public void rLineTo(float, float);
method public void rMoveTo(float, float);
@@ -15196,6 +15202,27 @@
ctor public PathEffect();
}
+ public class PathIterator implements java.util.Iterator<android.graphics.PathIterator.Segment> {
+ method public boolean hasNext();
+ method @NonNull public int next(@NonNull float[], int);
+ method @NonNull public android.graphics.PathIterator.Segment next();
+ method @NonNull public int peek();
+ field public static final int VERB_CLOSE = 5; // 0x5
+ field public static final int VERB_CONIC = 3; // 0x3
+ field public static final int VERB_CUBIC = 4; // 0x4
+ field public static final int VERB_DONE = 6; // 0x6
+ field public static final int VERB_LINE = 1; // 0x1
+ field public static final int VERB_MOVE = 0; // 0x0
+ field public static final int VERB_QUAD = 2; // 0x2
+ }
+
+ public static class PathIterator.Segment {
+ ctor public PathIterator.Segment(@NonNull int, @NonNull float[], float);
+ method public float getConicWeight();
+ method @NonNull public float[] getPoints();
+ method @NonNull public int getVerb();
+ }
+
public class PathMeasure {
ctor public PathMeasure();
ctor public PathMeasure(android.graphics.Path, boolean);
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index afcaabe..052b9af 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -33,7 +33,7 @@
* (based on the paint's Style), or it can be used for clipping or to draw
* text on a path.
*/
-public class Path {
+public class Path implements Iterable<PathIterator.Segment> {
private static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
@@ -92,6 +92,17 @@
}
/**
+ * Returns an iterator over the segments of this path.
+ *
+ * @return the Iterator object
+ */
+ @NonNull
+ @Override
+ public PathIterator iterator() {
+ return new PathIterator(this);
+ }
+
+ /**
* The logical operations that can be performed when combining two paths.
*
* @see #op(Path, android.graphics.Path.Op)
@@ -383,6 +394,49 @@
}
/**
+ * Add a quadratic bezier from the last point, approaching control point
+ * (x1,y1), and ending at (x2,y2), weighted by <code>weight</code>. If no
+ * moveTo() call has been made for this contour, the first point is
+ * automatically set to (0,0).
+ *
+ * A weight of 1 is equivalent to calling {@link #quadTo(float, float, float, float)}.
+ * A weight of 0 is equivalent to calling {@link #lineTo(float, float)} to
+ * <code>(x1, y1)</code> followed by {@link #lineTo(float, float)} to <code>(x2, y2)</code>.
+ *
+ * @param x1 The x-coordinate of the control point on a conic curve
+ * @param y1 The y-coordinate of the control point on a conic curve
+ * @param x2 The x-coordinate of the end point on a conic curve
+ * @param y2 The y-coordinate of the end point on a conic curve
+ * @param weight The weight of the conic applied to the curve. A value of 1 is equivalent
+ * to a quadratic with the given control and anchor points and a value of 0 is
+ * equivalent to a line to the first and another line to the second point.
+ */
+ public void conicTo(float x1, float y1, float x2, float y2, float weight) {
+ nConicTo(mNativePath, x1, y1, x2, y2, weight);
+ }
+
+ /**
+ * Same as conicTo, 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 conic curve
+ * @param dy1 The amount to add to the y-coordinate of the last point on
+ * this contour, for the control point of a conic curve
+ * @param dx2 The amount to add to the x-coordinate of the last point on
+ * this contour, for the end point of a conic curve
+ * @param dy2 The amount to add to the y-coordinate of the last point on
+ * this contour, for the end point of a conic curve
+ * @param weight The weight of the conic applied to the curve. A value of 1 is equivalent
+ * to a quadratic with the given control and anchor points and a value of 0 is
+ * equivalent to a line to the first and another line to the second point.
+ */
+ public void rConicTo(float dx1, float dy1, float dx2, float dy2, float weight) {
+ nRConicTo(mNativePath, dx1, dy1, dx2, dy2, weight);
+ }
+
+ /**
* 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).
@@ -736,6 +790,46 @@
return nApproximate(mNativePath, acceptableError);
}
+ /**
+ * Returns the generation ID of this path. The generation ID changes
+ * whenever the path is modified. This can be used as an efficient way to
+ * check if a path has changed.
+ *
+ * @return The current generation ID for this path
+ */
+ public int getGenerationId() {
+ return nGetGenerationID(mNativePath);
+ }
+
+ /**
+ * Two paths can be interpolated, by calling {@link #interpolate(Path, float, Path)}, if they
+ * have exactly the same structure. That is, both paths must have the same
+ * operations, in the same order. If any of the operations are
+ * of type {@link PathIterator#VERB_CONIC}, then the weights of those conics must also match.
+ *
+ * @param otherPath The other <code>Path</code> being interpolated to from this one.
+ * @return true if interpolation is possible, false otherwise
+ */
+ public boolean isInterpolatable(@NonNull Path otherPath) {
+ return nIsInterpolatable(mNativePath, otherPath.mNativePath);
+ }
+
+ /**
+ * This method will linearly interpolate from this path to <code>otherPath</code> given
+ * the interpolation parameter <code>t</code>, returning the result in
+ * <code>interpolatedPath</code>. Interpolation will only succeed if the structures of the
+ * two paths match exactly, as discussed in {@link #isInterpolatable(Path)}.
+ *
+ * @param otherPath The other <code>Path</code> being interpolated to.
+ * @param t The interpolation parameter. A value of 0 results in a <code>Path</code>
+ * equivalent to this path, a value of 1 results in one equivalent to
+ * <code>otherPath</code>.
+ * @param interpolatedPath The interpolated results.
+ */
+ public boolean interpolate(@NonNull Path otherPath, float t, @NonNull Path interpolatedPath) {
+ return nInterpolate(mNativePath, otherPath.mNativePath, t, interpolatedPath.mNativePath);
+ }
+
// ------------------ Regular JNI ------------------------
private static native long nInit();
@@ -750,6 +844,10 @@
private static native void nRLineTo(long nPath, float dx, float dy);
private static native void nQuadTo(long nPath, float x1, float y1, float x2, float y2);
private static native void nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2);
+ private static native void nConicTo(long nPath, float x1, float y1, float x2, float y2,
+ float weight);
+ private static native void nRConicTo(long nPath, float dx1, float dy1, float dx2, float dy2,
+ float weight);
private static native void nCubicTo(long nPath, float x1, float y1, float x2, float y2,
float x3, float y3);
private static native void nRCubicTo(long nPath, float x1, float y1, float x2, float y2,
@@ -777,6 +875,8 @@
private static native void nTransform(long nPath, long matrix);
private static native boolean nOp(long path1, long path2, int op, long result);
private static native float[] nApproximate(long nPath, float error);
+ private static native boolean nInterpolate(long startPath, long endPath, float t,
+ long interpolatedPath);
// ------------------ Fast JNI ------------------------
@@ -786,6 +886,10 @@
// ------------------ Critical JNI ------------------------
@CriticalNative
+ private static native int nGetGenerationID(long nativePath);
+ @CriticalNative
+ private static native boolean nIsInterpolatable(long startPath, long endPath);
+ @CriticalNative
private static native void nReset(long nPath);
@CriticalNative
private static native void nRewind(long nPath);
diff --git a/graphics/java/android/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java
new file mode 100644
index 0000000..33b9a47
--- /dev/null
+++ b/graphics/java/android/graphics/PathIterator.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2022 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 static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.system.VMRuntime;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.lang.annotation.Retention;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+
+/**
+ * <code>PathIterator</code> can be used to query a given {@link Path} object, to discover its
+ * operations and point values.
+ */
+public class PathIterator implements Iterator<PathIterator.Segment> {
+
+ private final float[] mPointsArray;
+ private final long mPointsAddress;
+ private int mCachedVerb = -1;
+ private boolean mDone = false;
+ private final long mNativeIterator;
+ private final Path mPath;
+ private final int mPathGenerationId;
+ private static final int POINT_ARRAY_SIZE = 8;
+
+ private static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ PathIterator.class.getClassLoader(), nGetFinalizer());
+
+ /**
+ * The <code>Verb</code> indicates the operation for a given segment of a path. These
+ * operations correspond exactly to the primitive operations on {@link Path}, such as
+ * {@link Path#moveTo(float, float)} and {@link Path#lineTo(float, float)}.
+ */
+ @Retention(SOURCE)
+ @IntDef({VERB_MOVE, VERB_LINE, VERB_QUAD, VERB_CONIC, VERB_CUBIC, VERB_CLOSE, VERB_DONE})
+ @interface Verb {}
+ // these must match the values in SkPath.h
+ public static final int VERB_MOVE = 0;
+ public static final int VERB_LINE = 1;
+ public static final int VERB_QUAD = 2;
+ public static final int VERB_CONIC = 3;
+ public static final int VERB_CUBIC = 4;
+ public static final int VERB_CLOSE = 5;
+ public static final int VERB_DONE = 6;
+
+ /**
+ * Returns a {@link PathIterator} object for this path, which can be used to query the
+ * data (operations and points) in the path. Iterators can only be used on Path objects
+ * that have not been modified since the iterator was created. Calling
+ * {@link #next(float[], int)}, {@link #next()}, or {@link #hasNext()} on an
+ * iterator for a modified path will result in a {@link ConcurrentModificationException}.
+ *
+ * @param path The {@link Path} for which this iterator can be queried.
+ */
+ PathIterator(@NonNull Path path) {
+ mPath = path;
+ mNativeIterator = nCreate(mPath.mNativePath);
+ mPathGenerationId = mPath.getGenerationId();
+ final VMRuntime runtime = VMRuntime.getRuntime();
+ mPointsArray = (float[]) runtime.newNonMovableArray(float.class, POINT_ARRAY_SIZE);
+ mPointsAddress = runtime.addressOf(mPointsArray);
+ sRegistry.registerNativeAllocation(this, mNativeIterator);
+ }
+
+ /**
+ * Returns the next verb in this iterator's {@link Path}, and fills entries in the
+ * <code>points</code> array with the point data (if any) for that operation.
+ * Each two floats represent the data for a single point of that operation.
+ * The number of pairs of floats supplied in the resulting array depends on the verb:
+ * <ul>
+ * <li>{@link #VERB_MOVE}: 1 pair (indices 0 to 1)</li>
+ * <li>{@link #VERB_LINE}: 2 pairs (indices 0 to 3)</li>
+ * <li>{@link #VERB_QUAD}: 3 pairs (indices 0 to 5)</li>
+ * <li>{@link #VERB_CONIC}: 3.5 pairs (indices 0 to 6), the seventh entry has the conic
+ * weight</li>
+ * <li>{@link #VERB_CUBIC}: 4 pairs (indices 0 to 7)</li>
+ * <li>{@link #VERB_CLOSE}: 0 pairs</li>
+ * <li>{@link #VERB_DONE}: 0 pairs</li>
+ * </ul>
+ * @param points The point data for this operation, must have at least
+ * 8 items available to hold up to 4 pairs of point values
+ * @param offset An offset into the <code>points</code> array where entries should be placed.
+ * @return the operation for the next element in the iteration
+ * @throws ArrayIndexOutOfBoundsException if the points array is too small
+ * @throws ConcurrentModificationException if the underlying path was modified
+ * since this iterator was created.
+ * @throws NoSuchElementException if the iteration has no more elements
+ */
+ @NonNull
+ public @Verb int next(@NonNull float[] points, int offset) {
+ if (points.length < offset + POINT_ARRAY_SIZE) {
+ throw new ArrayIndexOutOfBoundsException("points array must be able to "
+ + "hold at least 8 entries");
+ }
+ @Verb int returnVerb = getReturnVerb(mCachedVerb);
+ mCachedVerb = -1;
+ System.arraycopy(mPointsArray, 0, points, offset, POINT_ARRAY_SIZE);
+ return returnVerb;
+ }
+
+ /**
+ * Returns true if the there are more elements in this iterator to be returned.
+ * A return value of <code>false</code> means there are no more elements, and an
+ * ensuing call to {@link #next()} or {@link #next(float[], int)} )} will throw a
+ * {@link NoSuchElementException}.
+ *
+ * @return true if there are more elements to be iterated through, false otherwise
+ * @throws ConcurrentModificationException if the underlying path was modified
+ * since this iterator was created.
+ */
+ @Override
+ public boolean hasNext() {
+ try {
+ if (mCachedVerb == -1) {
+ mCachedVerb = nextInternal();
+ }
+ return true;
+ } catch (NoSuchElementException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the next verb in the iteration, or {@link #VERB_DONE} if there are no more
+ * elements.
+ *
+ * @return the next verb in the iteration, or {@link #VERB_DONE} if there are no more
+ * elements
+ * @throws ConcurrentModificationException if the underlying path was modified
+ * since this iterator was created.
+ */
+ @NonNull
+ public @Verb int peek() {
+ if (mPathGenerationId != mPath.getGenerationId()) {
+ throw new ConcurrentModificationException(
+ "Iterator cannot be used on modified Path");
+ }
+ if (mDone) {
+ return VERB_DONE;
+ }
+ return nPeek(mNativeIterator);
+ }
+
+ /**
+ * This is where the work is done for {@link #next()}. Using this internal method
+ * is helfpul for managing the cached segment used by {@link #hasNext()}.
+ *
+ * @return the segment to be returned by {@link #next()}
+ * @throws NoSuchElementException if the iteration has no more elements
+ */
+ @NonNull
+ private @Verb int nextInternal() {
+ if (mDone) {
+ throw new NoSuchElementException("No more path segments to iterate");
+ }
+ if (mPathGenerationId != mPath.getGenerationId()) {
+ throw new ConcurrentModificationException(
+ "Iterator cannot be used on modified Path");
+ }
+ @Verb int verb = nNext(mNativeIterator, mPointsAddress);
+ if (verb == VERB_DONE) {
+ mDone = true;
+ }
+ return verb;
+ }
+
+ /**
+ * Returns the next {@link Segment} element in this iterator.
+ *
+ * There are two versions of <code>next()</code>. This version is slightly more
+ * expensive at runtime, since it allocates a new {@link Segment} object with
+ * every call. The other version, {@link #next(float[], int)} requires no such allocation, but
+ * requires a little more manual effort to use.
+ *
+ * @return the next segment in this iterator
+ * @throws NoSuchElementException if the iteration has no more elements
+ * @throws ConcurrentModificationException if the underlying path was modified
+ * since this iterator was created.
+ */
+ @NonNull
+ @Override
+ public Segment next() {
+ @Verb int returnVerb = getReturnVerb(mCachedVerb);
+ mCachedVerb = -1;
+ float conicWeight = 0f;
+ if (returnVerb == VERB_CONIC) {
+ conicWeight = mPointsArray[6];
+ }
+ float[] returnPoints = new float[8];
+ System.arraycopy(mPointsArray, 0, returnPoints, 0, POINT_ARRAY_SIZE);
+ return new Segment(returnVerb, returnPoints, conicWeight);
+ }
+
+ private @Verb int getReturnVerb(int cachedVerb) {
+ switch (cachedVerb) {
+ case VERB_MOVE: return VERB_MOVE;
+ case VERB_LINE: return VERB_LINE;
+ case VERB_QUAD: return VERB_QUAD;
+ case VERB_CONIC: return VERB_CONIC;
+ case VERB_CUBIC: return VERB_CUBIC;
+ case VERB_CLOSE: return VERB_CLOSE;
+ case VERB_DONE: return VERB_DONE;
+ }
+ return nextInternal();
+ }
+
+ /**
+ * This class holds the data for a given segment in a path, as returned by
+ * {@link #next()}.
+ */
+ public static class Segment {
+ private final @Verb int mVerb;
+ private final float[] mPoints;
+ private final float mConicWeight;
+
+ /**
+ * The operation for this segment.
+ *
+ * @return the verb which indicates the operation happening in this segment
+ */
+ @NonNull
+ public @Verb int getVerb() {
+ return mVerb;
+ }
+
+ /**
+ * The point data for this segment.
+ *
+ * Each two floats represent the data for a single point of that operation.
+ * The number of pairs of floats supplied in the resulting array depends on the verb:
+ * <ul>
+ * <li>{@link #VERB_MOVE}: 1 pair (indices 0 to 1)</li>
+ * <li>{@link #VERB_LINE}: 2 pairs (indices 0 to 3)</li>
+ * <li>{@link #VERB_QUAD}: 3 pairs (indices 0 to 5)</li>
+ * <li>{@link #VERB_CONIC}: 4 pairs (indices 0 to 7), the last pair contains the
+ * conic weight twice</li>
+ * <li>{@link #VERB_CUBIC}: 4 pairs (indices 0 to 7)</li>
+ * <li>{@link #VERB_CLOSE}: 0 pairs</li>
+ * <li>{@link #VERB_DONE}: 0 pairs</li>
+ * </ul>
+ * @return the point data for this segment
+ */
+ @NonNull
+ public float[] getPoints() {
+ return mPoints;
+ }
+
+ /**
+ * The weight for the conic operation in this segment. If the verb in this segment
+ * is not equal to {@link #VERB_CONIC}, the weight value is undefined.
+ *
+ * @see Path#conicTo(float, float, float, float, float)
+ * @return the weight for the conic operation in this segment, if any
+ */
+ public float getConicWeight() {
+ return mConicWeight;
+ }
+
+ public Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
+ mVerb = verb;
+ mPoints = points;
+ mConicWeight = conicWeight;
+ }
+ }
+
+ // ------------------ Regular JNI ------------------------
+
+ private static native long nCreate(long nativePath);
+ private static native long nGetFinalizer();
+
+ // ------------------ Critical JNI ------------------------
+
+ @CriticalNative
+ private static native int nNext(long nativeIterator, long pointsAddress);
+
+ @CriticalNative
+ private static native int nPeek(long nativeIterator);
+}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index ad9aa6c..88f73d6 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -345,6 +345,7 @@
"jni/PaintFilter.cpp",
"jni/Path.cpp",
"jni/PathEffect.cpp",
+ "jni/PathIterator.cpp",
"jni/PathMeasure.cpp",
"jni/Picture.cpp",
"jni/Region.cpp",
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index 942c050..b7a1563 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -53,6 +53,7 @@
extern int register_android_graphics_Matrix(JNIEnv* env);
extern int register_android_graphics_Paint(JNIEnv* env);
extern int register_android_graphics_Path(JNIEnv* env);
+extern int register_android_graphics_PathIterator(JNIEnv* env);
extern int register_android_graphics_PathMeasure(JNIEnv* env);
extern int register_android_graphics_Picture(JNIEnv* env);
extern int register_android_graphics_Region(JNIEnv* env);
@@ -100,6 +101,7 @@
{"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)},
{"android.graphics.Path", REG_JNI(register_android_graphics_Path)},
{"android.graphics.PathEffect", REG_JNI(register_android_graphics_PathEffect)},
+ {"android.graphics.PathIterator", REG_JNI(register_android_graphics_PathIterator)},
{"android.graphics.PathMeasure", REG_JNI(register_android_graphics_PathMeasure)},
{"android.graphics.Picture", REG_JNI(register_android_graphics_Picture)},
{"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)},
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index e1f5abd7..39725a5 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -59,6 +59,7 @@
extern int register_android_graphics_Matrix(JNIEnv* env);
extern int register_android_graphics_Paint(JNIEnv* env);
extern int register_android_graphics_Path(JNIEnv* env);
+extern int register_android_graphics_PathIterator(JNIEnv* env);
extern int register_android_graphics_PathMeasure(JNIEnv* env);
extern int register_android_graphics_Picture(JNIEnv*);
extern int register_android_graphics_Region(JNIEnv* env);
@@ -94,59 +95,60 @@
};
#endif
-static const RegJNIRec gRegJNI[] = {
- REG_JNI(register_android_graphics_Canvas),
- // This needs to be before register_android_graphics_Graphics, or the latter
- // will not be able to find the jmethodID for ColorSpace.get().
- REG_JNI(register_android_graphics_ColorSpace),
- REG_JNI(register_android_graphics_Graphics),
- REG_JNI(register_android_graphics_Bitmap),
- REG_JNI(register_android_graphics_BitmapFactory),
- REG_JNI(register_android_graphics_BitmapRegionDecoder),
- REG_JNI(register_android_graphics_ByteBufferStreamAdaptor),
- REG_JNI(register_android_graphics_Camera),
- REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
- REG_JNI(register_android_graphics_CanvasProperty),
- REG_JNI(register_android_graphics_ColorFilter),
- REG_JNI(register_android_graphics_DrawFilter),
- REG_JNI(register_android_graphics_FontFamily),
- REG_JNI(register_android_graphics_HardwareRendererObserver),
- REG_JNI(register_android_graphics_ImageDecoder),
- REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
- REG_JNI(register_android_graphics_Interpolator),
- REG_JNI(register_android_graphics_MaskFilter),
- REG_JNI(register_android_graphics_Matrix),
- REG_JNI(register_android_graphics_Movie),
- REG_JNI(register_android_graphics_NinePatch),
- REG_JNI(register_android_graphics_Paint),
- REG_JNI(register_android_graphics_Path),
- REG_JNI(register_android_graphics_PathMeasure),
- REG_JNI(register_android_graphics_PathEffect),
- REG_JNI(register_android_graphics_Picture),
- REG_JNI(register_android_graphics_Region),
- REG_JNI(register_android_graphics_Shader),
- REG_JNI(register_android_graphics_RenderEffect),
- REG_JNI(register_android_graphics_TextureLayer),
- REG_JNI(register_android_graphics_Typeface),
- REG_JNI(register_android_graphics_YuvImage),
- REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory),
- REG_JNI(register_android_graphics_animation_RenderNodeAnimator),
- REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
- REG_JNI(register_android_graphics_drawable_VectorDrawable),
- REG_JNI(register_android_graphics_fonts_Font),
- REG_JNI(register_android_graphics_fonts_FontFamily),
- REG_JNI(register_android_graphics_pdf_PdfDocument),
- REG_JNI(register_android_graphics_pdf_PdfEditor),
- REG_JNI(register_android_graphics_pdf_PdfRenderer),
- REG_JNI(register_android_graphics_text_MeasuredText),
- REG_JNI(register_android_graphics_text_LineBreaker),
- REG_JNI(register_android_graphics_text_TextShaper),
+ static const RegJNIRec gRegJNI[] = {
+ REG_JNI(register_android_graphics_Canvas),
+ // This needs to be before register_android_graphics_Graphics, or the latter
+ // will not be able to find the jmethodID for ColorSpace.get().
+ REG_JNI(register_android_graphics_ColorSpace),
+ REG_JNI(register_android_graphics_Graphics),
+ REG_JNI(register_android_graphics_Bitmap),
+ REG_JNI(register_android_graphics_BitmapFactory),
+ REG_JNI(register_android_graphics_BitmapRegionDecoder),
+ REG_JNI(register_android_graphics_ByteBufferStreamAdaptor),
+ REG_JNI(register_android_graphics_Camera),
+ REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
+ REG_JNI(register_android_graphics_CanvasProperty),
+ REG_JNI(register_android_graphics_ColorFilter),
+ REG_JNI(register_android_graphics_DrawFilter),
+ REG_JNI(register_android_graphics_FontFamily),
+ REG_JNI(register_android_graphics_HardwareRendererObserver),
+ REG_JNI(register_android_graphics_ImageDecoder),
+ REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
+ REG_JNI(register_android_graphics_Interpolator),
+ REG_JNI(register_android_graphics_MaskFilter),
+ REG_JNI(register_android_graphics_Matrix),
+ REG_JNI(register_android_graphics_Movie),
+ REG_JNI(register_android_graphics_NinePatch),
+ REG_JNI(register_android_graphics_Paint),
+ REG_JNI(register_android_graphics_Path),
+ REG_JNI(register_android_graphics_PathIterator),
+ REG_JNI(register_android_graphics_PathMeasure),
+ REG_JNI(register_android_graphics_PathEffect),
+ REG_JNI(register_android_graphics_Picture),
+ REG_JNI(register_android_graphics_Region),
+ REG_JNI(register_android_graphics_Shader),
+ REG_JNI(register_android_graphics_RenderEffect),
+ REG_JNI(register_android_graphics_TextureLayer),
+ REG_JNI(register_android_graphics_Typeface),
+ REG_JNI(register_android_graphics_YuvImage),
+ REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory),
+ REG_JNI(register_android_graphics_animation_RenderNodeAnimator),
+ REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
+ REG_JNI(register_android_graphics_drawable_VectorDrawable),
+ REG_JNI(register_android_graphics_fonts_Font),
+ REG_JNI(register_android_graphics_fonts_FontFamily),
+ REG_JNI(register_android_graphics_pdf_PdfDocument),
+ REG_JNI(register_android_graphics_pdf_PdfEditor),
+ REG_JNI(register_android_graphics_pdf_PdfRenderer),
+ REG_JNI(register_android_graphics_text_MeasuredText),
+ REG_JNI(register_android_graphics_text_LineBreaker),
+ REG_JNI(register_android_graphics_text_TextShaper),
- REG_JNI(register_android_util_PathParser),
- REG_JNI(register_android_view_RenderNode),
- REG_JNI(register_android_view_DisplayListCanvas),
- REG_JNI(register_android_view_ThreadedRenderer),
-};
+ REG_JNI(register_android_util_PathParser),
+ REG_JNI(register_android_view_RenderNode),
+ REG_JNI(register_android_view_DisplayListCanvas),
+ REG_JNI(register_android_view_ThreadedRenderer),
+ };
} // namespace android
diff --git a/libs/hwui/jni/Path.cpp b/libs/hwui/jni/Path.cpp
index d67bcf2..3694ce0 100644
--- a/libs/hwui/jni/Path.cpp
+++ b/libs/hwui/jni/Path.cpp
@@ -102,6 +102,18 @@
obj->rQuadTo(dx1, dy1, dx2, dy2);
}
+ static void conicTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1, jfloat x2,
+ jfloat y2, jfloat weight) {
+ SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
+ obj->conicTo(x1, y1, x2, y2, weight);
+ }
+
+ static void rConicTo(JNIEnv* env, jclass clazz, jlong objHandle, jfloat dx1, jfloat dy1,
+ jfloat dx2, jfloat dy2, jfloat weight) {
+ SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
+ obj->rConicTo(dx1, dy1, dx2, dy2, weight);
+ }
+
static void cubicTo__FFFFFF(JNIEnv* env, jclass clazz, jlong objHandle, jfloat x1, jfloat y1,
jfloat x2, jfloat y2, jfloat x3, jfloat y3) {
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
@@ -209,6 +221,14 @@
obj->setLastPt(dx, dy);
}
+ static jboolean interpolate(JNIEnv* env, jclass clazz, jlong startHandle, jlong endHandle,
+ jfloat t, jlong interpolatedHandle) {
+ SkPath* startPath = reinterpret_cast<SkPath*>(startHandle);
+ SkPath* endPath = reinterpret_cast<SkPath*>(endHandle);
+ SkPath* interpolatedPath = reinterpret_cast<SkPath*>(interpolatedHandle);
+ return startPath->interpolate(*endPath, t, interpolatedPath);
+ }
+
static void transform__MatrixPath(JNIEnv* env, jclass clazz, jlong objHandle, jlong matrixHandle,
jlong dstHandle) {
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
@@ -473,6 +493,16 @@
// ---------------- @CriticalNative -------------------------
+ static jint getGenerationID(CRITICAL_JNI_PARAMS_COMMA jlong pathHandle) {
+ return (reinterpret_cast<SkPath*>(pathHandle)->getGenerationID());
+ }
+
+ static jboolean isInterpolatable(CRITICAL_JNI_PARAMS_COMMA jlong startHandle, jlong endHandle) {
+ SkPath* startPath = reinterpret_cast<SkPath*>(startHandle);
+ SkPath* endPath = reinterpret_cast<SkPath*>(endHandle);
+ return startPath->isInterpolatable(*endPath);
+ }
+
static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) {
SkPath* obj = reinterpret_cast<SkPath*>(objHandle);
obj->reset();
@@ -506,48 +536,53 @@
};
static const JNINativeMethod methods[] = {
- {"nInit","()J", (void*) SkPathGlue::init},
- {"nInit","(J)J", (void*) SkPathGlue::init_Path},
- {"nGetFinalizer", "()J", (void*) SkPathGlue::getFinalizer},
- {"nSet","(JJ)V", (void*) SkPathGlue::set},
- {"nComputeBounds","(JLandroid/graphics/RectF;)V", (void*) SkPathGlue::computeBounds},
- {"nIncReserve","(JI)V", (void*) SkPathGlue::incReserve},
- {"nMoveTo","(JFF)V", (void*) SkPathGlue::moveTo__FF},
- {"nRMoveTo","(JFF)V", (void*) SkPathGlue::rMoveTo},
- {"nLineTo","(JFF)V", (void*) SkPathGlue::lineTo__FF},
- {"nRLineTo","(JFF)V", (void*) SkPathGlue::rLineTo},
- {"nQuadTo","(JFFFF)V", (void*) SkPathGlue::quadTo__FFFF},
- {"nRQuadTo","(JFFFF)V", (void*) SkPathGlue::rQuadTo},
- {"nCubicTo","(JFFFFFF)V", (void*) SkPathGlue::cubicTo__FFFFFF},
- {"nRCubicTo","(JFFFFFF)V", (void*) SkPathGlue::rCubicTo},
- {"nArcTo","(JFFFFFFZ)V", (void*) SkPathGlue::arcTo},
- {"nClose","(J)V", (void*) SkPathGlue::close},
- {"nAddRect","(JFFFFI)V", (void*) SkPathGlue::addRect},
- {"nAddOval","(JFFFFI)V", (void*) SkPathGlue::addOval},
- {"nAddCircle","(JFFFI)V", (void*) SkPathGlue::addCircle},
- {"nAddArc","(JFFFFFF)V", (void*) SkPathGlue::addArc},
- {"nAddRoundRect","(JFFFFFFI)V", (void*) SkPathGlue::addRoundRectXY},
- {"nAddRoundRect","(JFFFF[FI)V", (void*) SkPathGlue::addRoundRect8},
- {"nAddPath","(JJFF)V", (void*) SkPathGlue::addPath__PathFF},
- {"nAddPath","(JJ)V", (void*) SkPathGlue::addPath__Path},
- {"nAddPath","(JJJ)V", (void*) SkPathGlue::addPath__PathMatrix},
- {"nOffset","(JFF)V", (void*) SkPathGlue::offset__FF},
- {"nSetLastPoint","(JFF)V", (void*) SkPathGlue::setLastPoint},
- {"nTransform","(JJJ)V", (void*) SkPathGlue::transform__MatrixPath},
- {"nTransform","(JJ)V", (void*) SkPathGlue::transform__Matrix},
- {"nOp","(JJIJ)Z", (void*) SkPathGlue::op},
- {"nApproximate", "(JF)[F", (void*) SkPathGlue::approximate},
+ {"nInit", "()J", (void*)SkPathGlue::init},
+ {"nInit", "(J)J", (void*)SkPathGlue::init_Path},
+ {"nGetFinalizer", "()J", (void*)SkPathGlue::getFinalizer},
+ {"nSet", "(JJ)V", (void*)SkPathGlue::set},
+ {"nComputeBounds", "(JLandroid/graphics/RectF;)V", (void*)SkPathGlue::computeBounds},
+ {"nIncReserve", "(JI)V", (void*)SkPathGlue::incReserve},
+ {"nMoveTo", "(JFF)V", (void*)SkPathGlue::moveTo__FF},
+ {"nRMoveTo", "(JFF)V", (void*)SkPathGlue::rMoveTo},
+ {"nLineTo", "(JFF)V", (void*)SkPathGlue::lineTo__FF},
+ {"nRLineTo", "(JFF)V", (void*)SkPathGlue::rLineTo},
+ {"nQuadTo", "(JFFFF)V", (void*)SkPathGlue::quadTo__FFFF},
+ {"nRQuadTo", "(JFFFF)V", (void*)SkPathGlue::rQuadTo},
+ {"nConicTo", "(JFFFFF)V", (void*)SkPathGlue::conicTo},
+ {"nRConicTo", "(JFFFFF)V", (void*)SkPathGlue::rConicTo},
+ {"nCubicTo", "(JFFFFFF)V", (void*)SkPathGlue::cubicTo__FFFFFF},
+ {"nRCubicTo", "(JFFFFFF)V", (void*)SkPathGlue::rCubicTo},
+ {"nArcTo", "(JFFFFFFZ)V", (void*)SkPathGlue::arcTo},
+ {"nClose", "(J)V", (void*)SkPathGlue::close},
+ {"nAddRect", "(JFFFFI)V", (void*)SkPathGlue::addRect},
+ {"nAddOval", "(JFFFFI)V", (void*)SkPathGlue::addOval},
+ {"nAddCircle", "(JFFFI)V", (void*)SkPathGlue::addCircle},
+ {"nAddArc", "(JFFFFFF)V", (void*)SkPathGlue::addArc},
+ {"nAddRoundRect", "(JFFFFFFI)V", (void*)SkPathGlue::addRoundRectXY},
+ {"nAddRoundRect", "(JFFFF[FI)V", (void*)SkPathGlue::addRoundRect8},
+ {"nAddPath", "(JJFF)V", (void*)SkPathGlue::addPath__PathFF},
+ {"nAddPath", "(JJ)V", (void*)SkPathGlue::addPath__Path},
+ {"nAddPath", "(JJJ)V", (void*)SkPathGlue::addPath__PathMatrix},
+ {"nInterpolate", "(JJFJ)Z", (void*)SkPathGlue::interpolate},
+ {"nOffset", "(JFF)V", (void*)SkPathGlue::offset__FF},
+ {"nSetLastPoint", "(JFF)V", (void*)SkPathGlue::setLastPoint},
+ {"nTransform", "(JJJ)V", (void*)SkPathGlue::transform__MatrixPath},
+ {"nTransform", "(JJ)V", (void*)SkPathGlue::transform__Matrix},
+ {"nOp", "(JJIJ)Z", (void*)SkPathGlue::op},
+ {"nApproximate", "(JF)[F", (void*)SkPathGlue::approximate},
- // ------- @FastNative below here ----------------------
- {"nIsRect","(JLandroid/graphics/RectF;)Z", (void*) SkPathGlue::isRect},
+ // ------- @FastNative below here ----------------------
+ {"nIsRect", "(JLandroid/graphics/RectF;)Z", (void*)SkPathGlue::isRect},
- // ------- @CriticalNative below here ------------------
- {"nReset","(J)V", (void*) SkPathGlue::reset},
- {"nRewind","(J)V", (void*) SkPathGlue::rewind},
- {"nIsEmpty","(J)Z", (void*) SkPathGlue::isEmpty},
- {"nIsConvex","(J)Z", (void*) SkPathGlue::isConvex},
- {"nGetFillType","(J)I", (void*) SkPathGlue::getFillType},
- {"nSetFillType","(JI)V", (void*) SkPathGlue::setFillType},
+ // ------- @CriticalNative below here ------------------
+ {"nGetGenerationID", "(J)I", (void*)SkPathGlue::getGenerationID},
+ {"nIsInterpolatable", "(JJ)Z", (void*)SkPathGlue::isInterpolatable},
+ {"nReset", "(J)V", (void*)SkPathGlue::reset},
+ {"nRewind", "(J)V", (void*)SkPathGlue::rewind},
+ {"nIsEmpty", "(J)Z", (void*)SkPathGlue::isEmpty},
+ {"nIsConvex", "(J)Z", (void*)SkPathGlue::isConvex},
+ {"nGetFillType", "(J)I", (void*)SkPathGlue::getFillType},
+ {"nSetFillType", "(JI)V", (void*)SkPathGlue::setFillType},
};
int register_android_graphics_Path(JNIEnv* env) {
diff --git a/libs/hwui/jni/PathIterator.cpp b/libs/hwui/jni/PathIterator.cpp
new file mode 100644
index 0000000..3884342
--- /dev/null
+++ b/libs/hwui/jni/PathIterator.cpp
@@ -0,0 +1,81 @@
+/* libs/android_runtime/android/graphics/PathMeasure.cpp
+**
+** Copyright 2007, 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.
+*/
+
+#include <log/log.h>
+
+#include "GraphicsJNI.h"
+#include "SkPath.h"
+#include "SkPoint.h"
+
+namespace android {
+
+class SkPathIteratorGlue {
+public:
+ static void finalizer(SkPath::RawIter* obj) { delete obj; }
+
+ static jlong getFinalizer(JNIEnv* env, jclass clazz) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&finalizer));
+ }
+
+ static jlong create(JNIEnv* env, jobject clazz, jlong pathHandle) {
+ const SkPath* path = reinterpret_cast<SkPath*>(pathHandle);
+ return reinterpret_cast<jlong>(new SkPath::RawIter(*path));
+ }
+
+ // ---------------- @CriticalNative -------------------------
+
+ static jint peek(CRITICAL_JNI_PARAMS_COMMA jlong iteratorHandle) {
+ SkPath::RawIter* iterator = reinterpret_cast<SkPath::RawIter*>(iteratorHandle);
+ return iterator->peek();
+ }
+
+ static jint next(CRITICAL_JNI_PARAMS_COMMA jlong iteratorHandle, jlong pointsArray) {
+ static_assert(SkPath::kMove_Verb == 0, "SkPath::Verb unexpected index");
+ static_assert(SkPath::kLine_Verb == 1, "SkPath::Verb unexpected index");
+ static_assert(SkPath::kQuad_Verb == 2, "SkPath::Verb unexpected index");
+ static_assert(SkPath::kConic_Verb == 3, "SkPath::Verb unexpected index");
+ static_assert(SkPath::kCubic_Verb == 4, "SkPath::Verb unexpected index");
+ static_assert(SkPath::kClose_Verb == 5, "SkPath::Verb unexpected index");
+ static_assert(SkPath::kDone_Verb == 6, "SkPath::Verb unexpected index");
+
+ SkPath::RawIter* iterator = reinterpret_cast<SkPath::RawIter*>(iteratorHandle);
+ float* points = reinterpret_cast<float*>(pointsArray);
+ SkPath::Verb verb =
+ static_cast<SkPath::Verb>(iterator->next(reinterpret_cast<SkPoint*>(points)));
+ if (verb == SkPath::kConic_Verb) {
+ float weight = iterator->conicWeight();
+ points[6] = weight;
+ }
+ return static_cast<int>(verb);
+ }
+};
+
+static const JNINativeMethod methods[] = {
+ {"nCreate", "(J)J", (void*)SkPathIteratorGlue::create},
+ {"nGetFinalizer", "()J", (void*)SkPathIteratorGlue::getFinalizer},
+
+ // ------- @CriticalNative below here ------------------
+
+ {"nPeek", "(J)I", (void*)SkPathIteratorGlue::peek},
+ {"nNext", "(JJ)I", (void*)SkPathIteratorGlue::next},
+};
+
+int register_android_graphics_PathIterator(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "android/graphics/PathIterator", methods, NELEM(methods));
+}
+
+} // namespace android