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