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 invalidateCache() {
122    }
123
124    @Override
125    public void setEvaluator(TypeEvaluator evaluator) {
126    }
127
128    @Override
129    public Class getType() {
130        return PointF.class;
131    }
132
133    @Override
134    public Keyframes clone() {
135        Keyframes clone = null;
136        try {
137            clone = (Keyframes) super.clone();
138        } catch (CloneNotSupportedException e) {}
139        return clone;
140    }
141
142    private PointF pointForIndex(int index) {
143        int base = (index * NUM_COMPONENTS);
144        int xOffset = base + X_OFFSET;
145        int yOffset = base + Y_OFFSET;
146        mTempPointF.set(mKeyframeData[xOffset], mKeyframeData[yOffset]);
147        return mTempPointF;
148    }
149
150    private static float interpolate(float fraction, float startValue, float endValue) {
151        float diff = endValue - startValue;
152        return startValue + (diff * fraction);
153    }
154
155    /**
156     * Returns a FloatKeyframes for the X component of the Path.
157     * @return a FloatKeyframes for the X component of the Path.
158     */
159    public FloatKeyframes createXFloatKeyframes() {
160        return new FloatKeyframesBase() {
161            @Override
162            public float getFloatValue(float fraction) {
163                PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
164                return pointF.x;
165            }
166        };
167    }
168
169    /**
170     * Returns a FloatKeyframes for the Y component of the Path.
171     * @return a FloatKeyframes for the Y component of the Path.
172     */
173    public FloatKeyframes createYFloatKeyframes() {
174        return new FloatKeyframesBase() {
175            @Override
176            public float getFloatValue(float fraction) {
177                PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
178                return pointF.y;
179            }
180        };
181    }
182
183    /**
184     * Returns an IntKeyframes for the X component of the Path.
185     * @return an IntKeyframes for the X component of the Path.
186     */
187    public IntKeyframes createXIntKeyframes() {
188        return new IntKeyframesBase() {
189            @Override
190            public int getIntValue(float fraction) {
191                PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
192                return Math.round(pointF.x);
193            }
194        };
195    }
196
197    /**
198     * Returns an IntKeyframeSet for the Y component of the Path.
199     * @return an IntKeyframeSet for the Y component of the Path.
200     */
201    public IntKeyframes createYIntKeyframes() {
202        return new IntKeyframesBase() {
203            @Override
204            public int getIntValue(float fraction) {
205                PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
206                return Math.round(pointF.y);
207            }
208        };
209    }
210
211    private abstract static class SimpleKeyframes implements Keyframes {
212        @Override
213        public void setEvaluator(TypeEvaluator evaluator) {
214        }
215
216        @Override
217        public void invalidateCache() {
218        }
219
220        @Override
221        public ArrayList<Keyframe> getKeyframes() {
222            return EMPTY_KEYFRAMES;
223        }
224
225        @Override
226        public Keyframes clone() {
227            Keyframes clone = null;
228            try {
229                clone = (Keyframes) super.clone();
230            } catch (CloneNotSupportedException e) {}
231            return clone;
232        }
233    }
234
235    abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes {
236        @Override
237        public Class getType() {
238            return Integer.class;
239        }
240
241        @Override
242        public Object getValue(float fraction) {
243            return getIntValue(fraction);
244        }
245    }
246
247    abstract static class FloatKeyframesBase extends SimpleKeyframes
248            implements FloatKeyframes {
249        @Override
250        public Class getType() {
251            return Float.class;
252        }
253
254        @Override
255        public Object getValue(float fraction) {
256            return getFloatValue(fraction);
257        }
258    }
259}
260