1/*
2 * Copyright (C) 2014 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 */
16package android.animation;
17
18import android.graphics.Path;
19import android.graphics.PointF;
20
21import java.util.ArrayList;
22
23/**
24 * PathKeyframes relies on approximating the Path as a series of line segments.
25 * The line segments are recursively divided until there is less than 1/2 pixel error
26 * between the lines and the curve. Each point of the line segment is converted
27 * to a Keyframe and a linear interpolation between Keyframes creates a good approximation
28 * of the curve.
29 * <p>
30 * PathKeyframes is optimized to reduce the number of objects created when there are
31 * many keyframes for a curve.
32 * </p>
33 * <p>
34 * Typically, the returned type is a PointF, but the individual components can be extracted
35 * as either an IntKeyframes or FloatKeyframes.
36 * </p>
37 * @hide
38 */
39public class PathKeyframes implements Keyframes {
40    private static final int FRACTION_OFFSET = 0;
41    private static final int X_OFFSET = 1;
42    private static final int Y_OFFSET = 2;
43    private static final int NUM_COMPONENTS = 3;
44    private static final ArrayList<Keyframe> EMPTY_KEYFRAMES = new ArrayList<Keyframe>();
45
46    private PointF mTempPointF = new PointF();
47    private float[] mKeyframeData;
48
49    public PathKeyframes(Path path) {
50        this(path, 0.5f);
51    }
52
53    public PathKeyframes(Path path, float error) {
54        if (path == null || path.isEmpty()) {
55            throw new IllegalArgumentException("The path must not be null or empty");
56        }
57        mKeyframeData = path.approximate(error);
58    }
59
60    @Override
61    public ArrayList<Keyframe> getKeyframes() {
62        return EMPTY_KEYFRAMES;
63    }
64
65    @Override
66    public Object getValue(float fraction) {
67        int numPoints = mKeyframeData.length / 3;
68        if (fraction < 0) {
69            return interpolateInRange(fraction, 0, 1);
70        } else if (fraction > 1) {
71            return interpolateInRange(fraction, numPoints - 2, numPoints - 1);
72        } else if (fraction == 0) {
73            return pointForIndex(0);
74        } else if (fraction == 1) {
75            return pointForIndex(numPoints - 1);
76        } else {
77            // Binary search for the correct section
78            int low = 0;
79            int high = numPoints - 1;
80
81            while (low <= high) {
82                int mid = (low + high) / 2;
83                float midFraction = mKeyframeData[(mid * NUM_COMPONENTS) + FRACTION_OFFSET];
84
85                if (fraction < midFraction) {
86                    high = mid - 1;
87                } else if (fraction > midFraction) {
88                    low = mid + 1;
89                } else {
90                    return pointForIndex(mid);
91                }
92            }
93
94            // now high is below the fraction and low is above the fraction
95            return interpolateInRange(fraction, high, low);
96        }
97    }
98
99    private PointF interpolateInRange(float fraction, int startIndex, int endIndex) {
100        int startBase = (startIndex * NUM_COMPONENTS);
101        int endBase = (endIndex * NUM_COMPONENTS);
102
103        float startFraction = mKeyframeData[startBase + FRACTION_OFFSET];
104        float endFraction = mKeyframeData[endBase + FRACTION_OFFSET];
105
106        float intervalFraction = (fraction - startFraction)/(endFraction - startFraction);
107
108        float startX = mKeyframeData[startBase + X_OFFSET];
109        float endX = mKeyframeData[endBase + X_OFFSET];
110        float startY = mKeyframeData[startBase + Y_OFFSET];
111        float endY = mKeyframeData[endBase + Y_OFFSET];
112
113        float x = interpolate(intervalFraction, startX, endX);
114        float y = interpolate(intervalFraction, startY, endY);
115
116        mTempPointF.set(x, y);
117        return mTempPointF;
118    }
119
120    @Override
121    public void setEvaluator(TypeEvaluator evaluator) {
122    }
123
124    @Override
125    public Class getType() {
126        return PointF.class;
127    }
128
129    @Override
130    public Keyframes clone() {
131        Keyframes clone = null;
132        try {
133            clone = (Keyframes) super.clone();
134        } catch (CloneNotSupportedException e) {}
135        return clone;
136    }
137
138    private PointF pointForIndex(int index) {
139        int base = (index * NUM_COMPONENTS);
140        int xOffset = base + X_OFFSET;
141        int yOffset = base + Y_OFFSET;
142        mTempPointF.set(mKeyframeData[xOffset], mKeyframeData[yOffset]);
143        return mTempPointF;
144    }
145
146    private static float interpolate(float fraction, float startValue, float endValue) {
147        float diff = endValue - startValue;
148        return startValue + (diff * fraction);
149    }
150
151    /**
152     * Returns a FloatKeyframes for the X component of the Path.
153     * @return a FloatKeyframes for the X component of the Path.
154     */
155    public FloatKeyframes createXFloatKeyframes() {
156        return new FloatKeyframesBase() {
157            @Override
158            public float getFloatValue(float fraction) {
159                PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
160                return pointF.x;
161            }
162        };
163    }
164
165    /**
166     * Returns a FloatKeyframes for the Y component of the Path.
167     * @return a FloatKeyframes for the Y component of the Path.
168     */
169    public FloatKeyframes createYFloatKeyframes() {
170        return new FloatKeyframesBase() {
171            @Override
172            public float getFloatValue(float fraction) {
173                PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
174                return pointF.y;
175            }
176        };
177    }
178
179    /**
180     * Returns an IntKeyframes for the X component of the Path.
181     * @return an IntKeyframes for the X component of the Path.
182     */
183    public IntKeyframes createXIntKeyframes() {
184        return new IntKeyframesBase() {
185            @Override
186            public int getIntValue(float fraction) {
187                PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
188                return Math.round(pointF.x);
189            }
190        };
191    }
192
193    /**
194     * Returns an IntKeyframeSet for the Y component of the Path.
195     * @return an IntKeyframeSet for the Y component of the Path.
196     */
197    public IntKeyframes createYIntKeyframes() {
198        return new IntKeyframesBase() {
199            @Override
200            public int getIntValue(float fraction) {
201                PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
202                return Math.round(pointF.y);
203            }
204        };
205    }
206
207    private abstract static class SimpleKeyframes implements Keyframes {
208        @Override
209        public void setEvaluator(TypeEvaluator evaluator) {
210        }
211
212        @Override
213        public ArrayList<Keyframe> getKeyframes() {
214            return EMPTY_KEYFRAMES;
215        }
216
217        @Override
218        public Keyframes clone() {
219            Keyframes clone = null;
220            try {
221                clone = (Keyframes) super.clone();
222            } catch (CloneNotSupportedException e) {}
223            return clone;
224        }
225    }
226
227    abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes {
228        @Override
229        public Class getType() {
230            return Integer.class;
231        }
232
233        @Override
234        public Object getValue(float fraction) {
235            return getIntValue(fraction);
236        }
237    }
238
239    abstract static class FloatKeyframesBase extends SimpleKeyframes
240            implements FloatKeyframes {
241        @Override
242        public Class getType() {
243            return Float.class;
244        }
245
246        @Override
247        public Object getValue(float fraction) {
248            return getFloatValue(fraction);
249        }
250    }
251}
252