1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.qs;
16 
17 import android.graphics.Path;
18 import android.view.animation.BaseInterpolator;
19 import android.view.animation.Interpolator;
20 
21 public class PathInterpolatorBuilder {
22 
23     // This governs how accurate the approximation of the Path is.
24     private static final float PRECISION = 0.002f;
25 
26     private float[] mX; // x coordinates in the line
27     private float[] mY; // y coordinates in the line
28     private float[] mDist; // Cumulative percentage length of the line
29 
PathInterpolatorBuilder(Path path)30     public PathInterpolatorBuilder(Path path) {
31         initPath(path);
32     }
33 
PathInterpolatorBuilder(float controlX, float controlY)34     public PathInterpolatorBuilder(float controlX, float controlY) {
35         initQuad(controlX, controlY);
36     }
37 
PathInterpolatorBuilder(float controlX1, float controlY1, float controlX2, float controlY2)38     public PathInterpolatorBuilder(float controlX1, float controlY1, float controlX2,
39             float controlY2) {
40         initCubic(controlX1, controlY1, controlX2, controlY2);
41     }
42 
initQuad(float controlX, float controlY)43     private void initQuad(float controlX, float controlY) {
44         Path path = new Path();
45         path.moveTo(0, 0);
46         path.quadTo(controlX, controlY, 1f, 1f);
47         initPath(path);
48     }
49 
initCubic(float x1, float y1, float x2, float y2)50     private void initCubic(float x1, float y1, float x2, float y2) {
51         Path path = new Path();
52         path.moveTo(0, 0);
53         path.cubicTo(x1, y1, x2, y2, 1f, 1f);
54         initPath(path);
55     }
56 
initPath(Path path)57     private void initPath(Path path) {
58         float[] pointComponents = path.approximate(PRECISION);
59 
60         int numPoints = pointComponents.length / 3;
61         if (pointComponents[1] != 0 || pointComponents[2] != 0
62                 || pointComponents[pointComponents.length - 2] != 1
63                 || pointComponents[pointComponents.length - 1] != 1) {
64             throw new IllegalArgumentException("The Path must start at (0,0) and end at (1,1)");
65         }
66 
67         mX = new float[numPoints];
68         mY = new float[numPoints];
69         mDist = new float[numPoints];
70         float prevX = 0;
71         float prevFraction = 0;
72         int componentIndex = 0;
73         for (int i = 0; i < numPoints; i++) {
74             float fraction = pointComponents[componentIndex++];
75             float x = pointComponents[componentIndex++];
76             float y = pointComponents[componentIndex++];
77             if (fraction == prevFraction && x != prevX) {
78                 throw new IllegalArgumentException(
79                         "The Path cannot have discontinuity in the X axis.");
80             }
81             if (x < prevX) {
82                 throw new IllegalArgumentException("The Path cannot loop back on itself.");
83             }
84             mX[i] = x;
85             mY[i] = y;
86             if (i > 0) {
87                 float dx = mX[i] - mX[i - 1];
88                 float dy = mY[i] - mY[i - 1];
89                 float dist = (float) Math.sqrt(dx * dx + dy * dy);
90                 mDist[i] = mDist[i - 1] + dist;
91             }
92             prevX = x;
93             prevFraction = fraction;
94         }
95         // Scale down dist to 0-1.
96         float max = mDist[mDist.length - 1];
97         for (int i = 0; i < numPoints; i++) {
98             mDist[i] /= max;
99         }
100     }
101 
getXInterpolator()102     public Interpolator getXInterpolator() {
103         return new PathInterpolator(mDist, mX);
104     }
105 
getYInterpolator()106     public Interpolator getYInterpolator() {
107         return new PathInterpolator(mDist, mY);
108     }
109 
110     private static class PathInterpolator extends BaseInterpolator {
111         private final float[] mX; // x coordinates in the line
112         private final float[] mY; // y coordinates in the line
113 
PathInterpolator(float[] xs, float[] ys)114         private PathInterpolator(float[] xs, float[] ys) {
115             mX = xs;
116             mY = ys;
117         }
118 
119         @Override
getInterpolation(float t)120         public float getInterpolation(float t) {
121             if (t <= 0) {
122                 return 0;
123             } else if (t >= 1) {
124                 return 1;
125             }
126             // Do a binary search for the correct x to interpolate between.
127             int startIndex = 0;
128             int endIndex = mX.length - 1;
129 
130             while (endIndex - startIndex > 1) {
131                 int midIndex = (startIndex + endIndex) / 2;
132                 if (t < mX[midIndex]) {
133                     endIndex = midIndex;
134                 } else {
135                     startIndex = midIndex;
136                 }
137             }
138 
139             float xRange = mX[endIndex] - mX[startIndex];
140             if (xRange == 0) {
141                 return mY[startIndex];
142             }
143 
144             float tInRange = t - mX[startIndex];
145             float fraction = tInRange / xRange;
146 
147             float startY = mY[startIndex];
148             float endY = mY[endIndex];
149             return startY + (fraction * (endY - startY));
150         }
151     }
152 
153 }
154