1 /*
2  * Copyright (C) 2015 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 com.android.systemui.recents.misc;
18 
19 import android.graphics.Path;
20 import android.view.animation.BaseInterpolator;
21 import android.view.animation.Interpolator;
22 
23 /**
24  * An interpolator that can traverse a Path. The x coordinate along the <code>Path</code>
25  * is the input value and the output is the y coordinate of the line at that point.
26  * This means that the Path must conform to a function <code>y = f(x)</code>.
27  *
28  * <p>The <code>Path</code> must not have gaps in the x direction and must not
29  * loop back on itself such that there can be two points sharing the same x coordinate.
30  * It is alright to have a disjoint line in the vertical direction:</p>
31  * <p><blockquote><pre>
32  *     Path path = new Path();
33  *     path.lineTo(0.25f, 0.25f);
34  *     path.moveTo(0.25f, 0.5f);
35  *     path.lineTo(1f, 1f);
36  * </pre></blockquote></p>
37  */
38 public class FreePathInterpolator extends BaseInterpolator {
39 
40     // This governs how accurate the approximation of the Path is.
41     private static final float PRECISION = 0.002f;
42 
43     private float[] mX;
44     private float[] mY;
45     private float mArcLength;
46 
47     /**
48      * Create an interpolator for an arbitrary <code>Path</code>.
49      *
50      * @param path The <code>Path</code> to use to make the line representing the interpolator.
51      */
FreePathInterpolator(Path path)52     public FreePathInterpolator(Path path) {
53         initPath(path);
54     }
55 
initPath(Path path)56     private void initPath(Path path) {
57         float[] pointComponents = path.approximate(PRECISION);
58 
59         int numPoints = pointComponents.length / 3;
60 
61         mX = new float[numPoints];
62         mY = new float[numPoints];
63         mArcLength = 0;
64         float prevX = 0;
65         float prevY = 0;
66         float prevFraction = 0;
67         int componentIndex = 0;
68         for (int i = 0; i < numPoints; i++) {
69             float fraction = pointComponents[componentIndex++];
70             float x = pointComponents[componentIndex++];
71             float y = pointComponents[componentIndex++];
72             if (fraction == prevFraction && x != prevX) {
73                 throw new IllegalArgumentException(
74                         "The Path cannot have discontinuity in the X axis.");
75             }
76             if (x < prevX) {
77                 throw new IllegalArgumentException("The Path cannot loop back on itself.");
78             }
79             mX[i] = x;
80             mY[i] = y;
81             mArcLength += Math.hypot(x - prevX, y - prevY);
82             prevX = x;
83             prevY = y;
84             prevFraction = fraction;
85         }
86     }
87 
88     /**
89      * Using the line in the Path in this interpolator that can be described as
90      * <code>y = f(x)</code>, finds the y coordinate of the line given <code>t</code>
91      * as the x coordinate.
92      *
93      * @param t Treated as the x coordinate along the line.
94      * @return The y coordinate of the Path along the line where x = <code>t</code>.
95      * @see Interpolator#getInterpolation(float)
96      */
97     @Override
getInterpolation(float t)98     public float getInterpolation(float t) {
99         int startIndex = 0;
100         int endIndex = mX.length - 1;
101 
102         // Return early if out of bounds
103         if (t <= 0) {
104             return mY[startIndex];
105         } else if (t >= 1) {
106             return mY[endIndex];
107         }
108 
109         // Do a binary search for the correct x to interpolate between.
110         while (endIndex - startIndex > 1) {
111             int midIndex = (startIndex + endIndex) / 2;
112             if (t < mX[midIndex]) {
113                 endIndex = midIndex;
114             } else {
115                 startIndex = midIndex;
116             }
117         }
118 
119         float xRange = mX[endIndex] - mX[startIndex];
120         if (xRange == 0) {
121             return mY[startIndex];
122         }
123 
124         float tInRange = t - mX[startIndex];
125         float fraction = tInRange / xRange;
126 
127         float startY = mY[startIndex];
128         float endY = mY[endIndex];
129         return startY + (fraction * (endY - startY));
130     }
131 
132     /**
133      * Finds the x that provides the given <code>y = f(x)</code>.
134      *
135      * @param y a value from (0,1) that is in this path.
136      */
getX(float y)137     public float getX(float y) {
138         int startIndex = 0;
139         int endIndex = mY.length - 1;
140 
141         // Return early if out of bounds
142         if (y <= 0) {
143             return mX[endIndex];
144         } else if (y >= 1) {
145             return mX[startIndex];
146         }
147 
148         // Do a binary search for index that bounds the y
149         while (endIndex - startIndex > 1) {
150             int midIndex = (startIndex + endIndex) / 2;
151             if (y < mY[midIndex]) {
152                 startIndex = midIndex;
153             } else {
154                 endIndex = midIndex;
155             }
156         }
157 
158         float yRange = mY[endIndex] - mY[startIndex];
159         if (yRange == 0) {
160             return mX[startIndex];
161         }
162 
163         float tInRange = y - mY[startIndex];
164         float fraction = tInRange / yRange;
165 
166         float startX = mX[startIndex];
167         float endX = mX[endIndex];
168         return startX + (fraction * (endX - startX));
169     }
170 
171     /**
172      * Returns the arclength of the path we are interpolating.
173      */
getArcLength()174     public float getArcLength() {
175         return mArcLength;
176     }
177 }