1 /* 2 * Copyright (C) 2022 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 static java.lang.annotation.RetentionPolicy.SOURCE; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 24 import dalvik.annotation.optimization.CriticalNative; 25 import dalvik.system.VMRuntime; 26 27 import libcore.util.NativeAllocationRegistry; 28 29 import java.lang.annotation.Retention; 30 import java.util.ConcurrentModificationException; 31 import java.util.Iterator; 32 33 /** 34 * <code>PathIterator</code> can be used to query a given {@link Path} object, to discover its 35 * operations and point values. 36 */ 37 public class PathIterator implements Iterator<PathIterator.Segment> { 38 39 private final float[] mPointsArray; 40 private final long mPointsAddress; 41 private int mCachedVerb = -1; 42 private boolean mDone = false; 43 private final long mNativeIterator; 44 private final Path mPath; 45 private final int mPathGenerationId; 46 private static final int POINT_ARRAY_SIZE = 8; 47 48 private static final NativeAllocationRegistry sRegistry = 49 NativeAllocationRegistry.createMalloced( 50 PathIterator.class.getClassLoader(), nGetFinalizer()); 51 52 /** 53 * The <code>Verb</code> indicates the operation for a given segment of a path. These 54 * operations correspond exactly to the primitive operations on {@link Path}, such as 55 * {@link Path#moveTo(float, float)} and {@link Path#lineTo(float, float)}, except for 56 * {@link #VERB_DONE}, which means that there are no more operations in this path. 57 */ 58 @Retention(SOURCE) 59 @IntDef({VERB_MOVE, VERB_LINE, VERB_QUAD, VERB_CONIC, VERB_CUBIC, VERB_CLOSE, VERB_DONE}) 60 @interface Verb {} 61 // these must match the values in SkPath.h 62 public static final int VERB_MOVE = 0; 63 public static final int VERB_LINE = 1; 64 public static final int VERB_QUAD = 2; 65 public static final int VERB_CONIC = 3; 66 public static final int VERB_CUBIC = 4; 67 public static final int VERB_CLOSE = 5; 68 public static final int VERB_DONE = 6; 69 70 /** 71 * Returns a {@link PathIterator} object for this path, which can be used to query the 72 * data (operations and points) in the path. Iterators can only be used on Path objects 73 * that have not been modified since the iterator was created. Calling 74 * {@link #next(float[], int)}, {@link #next()}, or {@link #hasNext()} on an 75 * iterator for a modified path will result in a {@link ConcurrentModificationException}. 76 * 77 * @param path The {@link Path} for which this iterator can be queried. 78 */ PathIterator(@onNull Path path)79 PathIterator(@NonNull Path path) { 80 mPath = path; 81 mNativeIterator = nCreate(mPath.mNativePath); 82 mPathGenerationId = mPath.getGenerationId(); 83 final VMRuntime runtime = VMRuntime.getRuntime(); 84 mPointsArray = (float[]) runtime.newNonMovableArray(float.class, POINT_ARRAY_SIZE); 85 mPointsAddress = runtime.addressOf(mPointsArray); 86 sRegistry.registerNativeAllocation(this, mNativeIterator); 87 } 88 89 /** 90 * Returns the next verb in this iterator's {@link Path}, and fills entries in the 91 * <code>points</code> array with the point data (if any) for that operation. 92 * Each two floats represent the data for a single point of that operation. 93 * The number of pairs of floats supplied in the resulting array depends on the verb: 94 * <ul> 95 * <li>{@link #VERB_MOVE}: 1 pair (indices 0 to 1)</li> 96 * <li>{@link #VERB_LINE}: 2 pairs (indices 0 to 3)</li> 97 * <li>{@link #VERB_QUAD}: 3 pairs (indices 0 to 5)</li> 98 * <li>{@link #VERB_CONIC}: 3.5 pairs (indices 0 to 6), the seventh entry has the conic 99 * weight</li> 100 * <li>{@link #VERB_CUBIC}: 4 pairs (indices 0 to 7)</li> 101 * <li>{@link #VERB_CLOSE}: 0 pairs</li> 102 * <li>{@link #VERB_DONE}: 0 pairs</li> 103 * </ul> 104 * @param points The point data for this operation, must have at least 105 * 8 items available to hold up to 4 pairs of point values 106 * @param offset An offset into the <code>points</code> array where entries should be placed. 107 * @return the operation for the next element in the iteration 108 * @throws ArrayIndexOutOfBoundsException if the points array is too small 109 * @throws ConcurrentModificationException if the underlying path was modified 110 * since this iterator was created. 111 */ 112 @NonNull next(@onNull float[] points, int offset)113 public @Verb int next(@NonNull float[] points, int offset) { 114 if (points.length < offset + POINT_ARRAY_SIZE) { 115 throw new ArrayIndexOutOfBoundsException("points array must be able to " 116 + "hold at least 8 entries"); 117 } 118 @Verb int returnVerb = getReturnVerb(mCachedVerb); 119 mCachedVerb = -1; 120 System.arraycopy(mPointsArray, 0, points, offset, POINT_ARRAY_SIZE); 121 return returnVerb; 122 } 123 124 /** 125 * Returns true if the there are more elements in this iterator to be returned. 126 * A return value of <code>false</code> means there are no more elements, and an 127 * ensuing call to {@link #next()} or {@link #next(float[], int)} )} will return 128 * {@link #VERB_DONE}. 129 * 130 * @return true if there are more elements to be iterated through, false otherwise 131 * @throws ConcurrentModificationException if the underlying path was modified 132 * since this iterator was created. 133 */ 134 @Override hasNext()135 public boolean hasNext() { 136 if (mCachedVerb == -1) { 137 mCachedVerb = nextInternal(); 138 } 139 return mCachedVerb != VERB_DONE; 140 } 141 142 /** 143 * Returns the next verb in the iteration, or {@link #VERB_DONE} if there are no more 144 * elements. 145 * 146 * @return the next verb in the iteration, or {@link #VERB_DONE} if there are no more 147 * elements 148 * @throws ConcurrentModificationException if the underlying path was modified 149 * since this iterator was created. 150 */ 151 @NonNull peek()152 public @Verb int peek() { 153 if (mPathGenerationId != mPath.getGenerationId()) { 154 throw new ConcurrentModificationException( 155 "Iterator cannot be used on modified Path"); 156 } 157 if (mDone) { 158 return VERB_DONE; 159 } 160 return nPeek(mNativeIterator); 161 } 162 163 /** 164 * This is where the work is done for {@link #next()}. Using this internal method 165 * is helfpul for managing the cached segment used by {@link #hasNext()}. 166 * 167 * @return the segment to be returned by {@link #next()} 168 * @throws ConcurrentModificationException if the underlying path was modified 169 * since this iterator was created. 170 */ 171 @NonNull nextInternal()172 private @Verb int nextInternal() { 173 if (mDone) { 174 return VERB_DONE; 175 } 176 if (mPathGenerationId != mPath.getGenerationId()) { 177 throw new ConcurrentModificationException( 178 "Iterator cannot be used on modified Path"); 179 } 180 @Verb int verb = nNext(mNativeIterator, mPointsAddress); 181 if (verb == VERB_DONE) { 182 mDone = true; 183 } 184 return verb; 185 } 186 187 /** 188 * Returns the next {@link Segment} element in this iterator. 189 * 190 * There are two versions of <code>next()</code>. This version is slightly more 191 * expensive at runtime, since it allocates a new {@link Segment} object with 192 * every call. The other version, {@link #next(float[], int)} requires no such allocation, but 193 * requires a little more manual effort to use. 194 * 195 * @return the next segment in this iterator 196 * @throws ConcurrentModificationException if the underlying path was modified 197 * since this iterator was created. 198 */ 199 @NonNull 200 @Override next()201 public Segment next() { 202 @Verb int returnVerb = getReturnVerb(mCachedVerb); 203 mCachedVerb = -1; 204 float conicWeight = 0f; 205 if (returnVerb == VERB_CONIC) { 206 conicWeight = mPointsArray[6]; 207 } 208 float[] returnPoints = new float[8]; 209 System.arraycopy(mPointsArray, 0, returnPoints, 0, POINT_ARRAY_SIZE); 210 return new Segment(returnVerb, returnPoints, conicWeight); 211 } 212 getReturnVerb(int cachedVerb)213 private @Verb int getReturnVerb(int cachedVerb) { 214 switch (cachedVerb) { 215 case VERB_MOVE: return VERB_MOVE; 216 case VERB_LINE: return VERB_LINE; 217 case VERB_QUAD: return VERB_QUAD; 218 case VERB_CONIC: return VERB_CONIC; 219 case VERB_CUBIC: return VERB_CUBIC; 220 case VERB_CLOSE: return VERB_CLOSE; 221 case VERB_DONE: return VERB_DONE; 222 } 223 return nextInternal(); 224 } 225 226 /** 227 * This class holds the data for a given segment in a path, as returned by 228 * {@link #next()}. 229 */ 230 public static class Segment { 231 private final @Verb int mVerb; 232 private final float[] mPoints; 233 private final float mConicWeight; 234 235 /** 236 * The operation for this segment. 237 * 238 * @return the verb which indicates the operation happening in this segment 239 */ 240 @NonNull getVerb()241 public @Verb int getVerb() { 242 return mVerb; 243 } 244 245 /** 246 * The point data for this segment. 247 * 248 * Each two floats represent the data for a single point of that operation. 249 * The number of pairs of floats supplied in the resulting array depends on the verb: 250 * <ul> 251 * <li>{@link #VERB_MOVE}: 1 pair (indices 0 to 1)</li> 252 * <li>{@link #VERB_LINE}: 2 pairs (indices 0 to 3)</li> 253 * <li>{@link #VERB_QUAD}: 3 pairs (indices 0 to 5)</li> 254 * <li>{@link #VERB_CONIC}: 4 pairs (indices 0 to 7), the last pair contains the 255 * conic weight twice</li> 256 * <li>{@link #VERB_CUBIC}: 4 pairs (indices 0 to 7)</li> 257 * <li>{@link #VERB_CLOSE}: 0 pairs</li> 258 * <li>{@link #VERB_DONE}: 0 pairs</li> 259 * </ul> 260 * @return the point data for this segment 261 */ 262 @NonNull getPoints()263 public float[] getPoints() { 264 return mPoints; 265 } 266 267 /** 268 * The weight for the conic operation in this segment. If the verb in this segment 269 * is not equal to {@link #VERB_CONIC}, the weight value is undefined. 270 * 271 * @see Path#conicTo(float, float, float, float, float) 272 * @return the weight for the conic operation in this segment, if any 273 */ getConicWeight()274 public float getConicWeight() { 275 return mConicWeight; 276 } 277 Segment(@onNull @erb int verb, @NonNull float[] points, float conicWeight)278 Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) { 279 mVerb = verb; 280 mPoints = points; 281 mConicWeight = conicWeight; 282 } 283 } 284 285 // ------------------ Regular JNI ------------------------ 286 nCreate(long nativePath)287 private static native long nCreate(long nativePath); nGetFinalizer()288 private static native long nGetFinalizer(); 289 290 // ------------------ Critical JNI ------------------------ 291 292 @CriticalNative nNext(long nativeIterator, long pointsAddress)293 private static native int nNext(long nativeIterator, long pointsAddress); 294 295 @CriticalNative nPeek(long nativeIterator)296 private static native int nPeek(long nativeIterator); 297 } 298