• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.airbnb.lottie.utils;
2 
3 import android.content.Context;
4 import android.content.res.Resources;
5 import android.graphics.Bitmap;
6 import android.graphics.Canvas;
7 import android.graphics.Color;
8 import android.graphics.Matrix;
9 import android.graphics.Paint;
10 import android.graphics.Path;
11 import android.graphics.PathMeasure;
12 import android.graphics.PointF;
13 import android.graphics.RectF;
14 import android.os.Build;
15 import android.provider.Settings;
16 
17 import androidx.annotation.Nullable;
18 
19 import com.airbnb.lottie.L;
20 import com.airbnb.lottie.animation.LPaint;
21 import com.airbnb.lottie.animation.content.TrimPathContent;
22 import com.airbnb.lottie.animation.keyframe.FloatKeyframeAnimation;
23 
24 import java.io.Closeable;
25 import java.io.InterruptedIOException;
26 import java.net.ProtocolException;
27 import java.net.SocketException;
28 import java.net.UnknownHostException;
29 import java.net.UnknownServiceException;
30 import java.nio.channels.ClosedChannelException;
31 
32 import javax.net.ssl.SSLException;
33 
34 public final class Utils {
35   public static final int SECOND_IN_NANOS = 1000000000;
36 
37   private static final PathMeasure pathMeasure = new PathMeasure();
38   private static final Path tempPath = new Path();
39   private static final Path tempPath2 = new Path();
40   private static final float[] points = new float[4];
41   private static final float INV_SQRT_2 = (float) (Math.sqrt(2) / 2.0);
42   private static float dpScale = -1;
43 
Utils()44   private Utils() {
45   }
46 
createPath(PointF startPoint, PointF endPoint, PointF cp1, PointF cp2)47   public static Path createPath(PointF startPoint, PointF endPoint, PointF cp1, PointF cp2) {
48     Path path = new Path();
49     path.moveTo(startPoint.x, startPoint.y);
50 
51     if (cp1 != null && cp2 != null && (cp1.length() != 0 || cp2.length() != 0)) {
52       path.cubicTo(
53           startPoint.x + cp1.x, startPoint.y + cp1.y,
54           endPoint.x + cp2.x, endPoint.y + cp2.y,
55           endPoint.x, endPoint.y);
56     } else {
57       path.lineTo(endPoint.x, endPoint.y);
58     }
59     return path;
60   }
61 
closeQuietly(Closeable closeable)62   public static void closeQuietly(Closeable closeable) {
63     if (closeable != null) {
64       try {
65         closeable.close();
66       } catch (RuntimeException rethrown) {
67         throw rethrown;
68       } catch (Exception ignored) {
69       }
70     }
71   }
72 
getScale(Matrix matrix)73   public static float getScale(Matrix matrix) {
74     points[0] = 0;
75     points[1] = 0;
76     // Use 1/sqrt(2) so that the hypotenuse is of length 1.
77     points[2] = INV_SQRT_2;
78     points[3] = INV_SQRT_2;
79     matrix.mapPoints(points);
80     float dx = points[2] - points[0];
81     float dy = points[3] - points[1];
82 
83     return (float) Math.hypot(dx, dy);
84   }
85 
hasZeroScaleAxis(Matrix matrix)86   public static boolean hasZeroScaleAxis(Matrix matrix) {
87     points[0] = 0;
88     points[1] = 0;
89     // Random numbers. The only way these should map to the same thing as 0,0 is if the scale is 0.
90     points[2] = 37394.729378f;
91     points[3] = 39575.2343807f;
92     matrix.mapPoints(points);
93     if (points[0] == points[2] || points[1] == points[3]) {
94       return true;
95     }
96     return false;
97   }
98 
applyTrimPathIfNeeded(Path path, @Nullable TrimPathContent trimPath)99   public static void applyTrimPathIfNeeded(Path path, @Nullable TrimPathContent trimPath) {
100     if (trimPath == null || trimPath.isHidden()) {
101       return;
102     }
103     float start = ((FloatKeyframeAnimation) trimPath.getStart()).getFloatValue();
104     float end = ((FloatKeyframeAnimation) trimPath.getEnd()).getFloatValue();
105     float offset = ((FloatKeyframeAnimation) trimPath.getOffset()).getFloatValue();
106     applyTrimPathIfNeeded(path, start / 100f, end / 100f, offset / 360f);
107   }
108 
applyTrimPathIfNeeded( Path path, float startValue, float endValue, float offsetValue)109   public static void applyTrimPathIfNeeded(
110       Path path, float startValue, float endValue, float offsetValue) {
111     L.beginSection("applyTrimPathIfNeeded");
112     pathMeasure.setPath(path, false);
113 
114     float length = pathMeasure.getLength();
115     if (startValue == 1f && endValue == 0f) {
116       L.endSection("applyTrimPathIfNeeded");
117       return;
118     }
119     if (length < 1f || Math.abs(endValue - startValue - 1) < .01) {
120       L.endSection("applyTrimPathIfNeeded");
121       return;
122     }
123     float start = length * startValue;
124     float end = length * endValue;
125     float newStart = Math.min(start, end);
126     float newEnd = Math.max(start, end);
127 
128     float offset = offsetValue * length;
129     newStart += offset;
130     newEnd += offset;
131 
132     // If the trim path has rotated around the path, we need to shift it back.
133     if (newStart >= length && newEnd >= length) {
134       newStart = MiscUtils.floorMod(newStart, length);
135       newEnd = MiscUtils.floorMod(newEnd, length);
136     }
137 
138     if (newStart < 0) {
139       newStart = MiscUtils.floorMod(newStart, length);
140     }
141     if (newEnd < 0) {
142       newEnd = MiscUtils.floorMod(newEnd, length);
143     }
144 
145     // If the start and end are equals, return an empty path.
146     if (newStart == newEnd) {
147       path.reset();
148       L.endSection("applyTrimPathIfNeeded");
149       return;
150     }
151 
152     if (newStart >= newEnd) {
153       newStart -= length;
154     }
155 
156     tempPath.reset();
157     pathMeasure.getSegment(
158         newStart,
159         newEnd,
160         tempPath,
161         true);
162 
163     if (newEnd > length) {
164       tempPath2.reset();
165       pathMeasure.getSegment(
166           0,
167           newEnd % length,
168           tempPath2,
169           true);
170       tempPath.addPath(tempPath2);
171     } else if (newStart < 0) {
172       tempPath2.reset();
173       pathMeasure.getSegment(
174           length + newStart,
175           length,
176           tempPath2,
177           true);
178       tempPath.addPath(tempPath2);
179     }
180     path.set(tempPath);
181     L.endSection("applyTrimPathIfNeeded");
182   }
183 
184   @SuppressWarnings("SameParameterValue")
isAtLeastVersion(int major, int minor, int patch, int minMajor, int minMinor, int minPatch)185   public static boolean isAtLeastVersion(int major, int minor, int patch, int minMajor, int minMinor, int
186       minPatch) {
187     if (major < minMajor) {
188       return false;
189     } else if (major > minMajor) {
190       return true;
191     }
192 
193     if (minor < minMinor) {
194       return false;
195     } else if (minor > minMinor) {
196       return true;
197     }
198 
199     return patch >= minPatch;
200   }
201 
hashFor(float a, float b, float c, float d)202   public static int hashFor(float a, float b, float c, float d) {
203     int result = 17;
204     if (a != 0) {
205       result = (int) (31 * result * a);
206     }
207     if (b != 0) {
208       result = (int) (31 * result * b);
209     }
210     if (c != 0) {
211       result = (int) (31 * result * c);
212     }
213     if (d != 0) {
214       result = (int) (31 * result * d);
215     }
216     return result;
217   }
218 
dpScale()219   public static float dpScale() {
220     if (dpScale == -1) {
221       dpScale = Resources.getSystem().getDisplayMetrics().density;
222     }
223     return dpScale;
224   }
225 
getAnimationScale(Context context)226   public static float getAnimationScale(Context context) {
227     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
228       return Settings.Global.getFloat(context.getContentResolver(),
229               Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f);
230     } else {
231       //noinspection deprecation
232       return Settings.System.getFloat(context.getContentResolver(),
233               Settings.System.ANIMATOR_DURATION_SCALE, 1.0f);
234     }
235   }
236 
237   /**
238    * Resize the bitmap to exactly the same size as the specified dimension, changing the aspect ratio if needed.
239    * Returns the original bitmap if the dimensions already match.
240    */
resizeBitmapIfNeeded(Bitmap bitmap, int width, int height)241   public static Bitmap resizeBitmapIfNeeded(Bitmap bitmap, int width, int height) {
242     if (bitmap.getWidth() == width && bitmap.getHeight() == height) {
243       return bitmap;
244     }
245     Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
246     bitmap.recycle();
247     return resizedBitmap;
248   }
249 
250   /**
251    * From http://vaibhavblogs.org/2012/12/common-java-networking-exceptions/
252    */
isNetworkException(Throwable e)253   public static boolean isNetworkException(Throwable e) {
254     return e instanceof SocketException || e instanceof ClosedChannelException ||
255         e instanceof InterruptedIOException || e instanceof ProtocolException ||
256         e instanceof SSLException || e instanceof UnknownHostException ||
257         e instanceof UnknownServiceException;
258   }
259 
saveLayerCompat(Canvas canvas, RectF rect, Paint paint)260   public static void saveLayerCompat(Canvas canvas, RectF rect, Paint paint) {
261     saveLayerCompat(canvas, rect, paint, Canvas.ALL_SAVE_FLAG);
262   }
263 
saveLayerCompat(Canvas canvas, RectF rect, Paint paint, int flag)264   public static void saveLayerCompat(Canvas canvas, RectF rect, Paint paint, int flag) {
265     L.beginSection("Utils#saveLayer");
266     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
267       // This method was deprecated in API level 26 and not recommended since 22, but its
268       // 2-parameter replacement is only available starting at API level 21.
269       canvas.saveLayer(rect, paint, flag);
270     } else {
271       canvas.saveLayer(rect, paint);
272     }
273     L.endSection("Utils#saveLayer");
274   }
275 
276   /**
277    * For testing purposes only. DO NOT USE IN PRODUCTION.
278    */
renderPath(Path path)279   public static Bitmap renderPath(Path path) {
280     RectF bounds = new RectF();
281     path.computeBounds(bounds, false);
282     Bitmap bitmap = Bitmap.createBitmap((int) bounds.right, (int) bounds.bottom, Bitmap.Config.ARGB_8888);
283     Canvas canvas = new Canvas(bitmap);
284     Paint paint = new LPaint();
285     paint.setAntiAlias(true);
286     paint.setColor(Color.BLUE);
287     canvas.drawPath(path, paint);
288     return bitmap;
289   }
290 }
291