1 package com.airbnb.lottie.manager; 2 import android.content.Context; 3 import android.graphics.Bitmap; 4 import android.graphics.BitmapFactory; 5 import android.graphics.drawable.Drawable; 6 import androidx.annotation.Nullable; 7 import android.text.TextUtils; 8 import android.util.Base64; 9 import android.view.View; 10 11 import com.airbnb.lottie.ImageAssetDelegate; 12 import com.airbnb.lottie.LottieImageAsset; 13 import com.airbnb.lottie.utils.Logger; 14 import com.airbnb.lottie.utils.Utils; 15 import java.io.IOException; 16 import java.io.InputStream; 17 import java.util.HashMap; 18 import java.util.Map; 19 20 public class ImageAssetManager { 21 private static final Object bitmapHashLock = new Object(); 22 private final Context context; 23 private final String imagesFolder; 24 @Nullable private ImageAssetDelegate delegate; 25 private final Map<String, LottieImageAsset> imageAssets; 26 ImageAssetManager(Drawable.Callback callback, String imagesFolder, ImageAssetDelegate delegate, Map<String, LottieImageAsset> imageAssets)27 public ImageAssetManager(Drawable.Callback callback, String imagesFolder, 28 ImageAssetDelegate delegate, Map<String, LottieImageAsset> imageAssets) { 29 if (!TextUtils.isEmpty(imagesFolder) && imagesFolder.charAt(imagesFolder.length() - 1) != '/') { 30 this.imagesFolder = imagesFolder + '/'; 31 } else { 32 this.imagesFolder = imagesFolder; 33 } 34 if (!(callback instanceof View)) { 35 Logger.warning("LottieDrawable must be inside of a view for images to work."); 36 this.imageAssets = new HashMap<>(); 37 context = null; 38 return; 39 } 40 41 context = ((View) callback).getContext(); 42 this.imageAssets = imageAssets; 43 setDelegate(delegate); 44 } 45 setDelegate(@ullable ImageAssetDelegate assetDelegate)46 public void setDelegate(@Nullable ImageAssetDelegate assetDelegate) { 47 this.delegate = assetDelegate; 48 } 49 50 /** 51 * Returns the previously set bitmap or null. 52 */ updateBitmap(String id, @Nullable Bitmap bitmap)53 @Nullable public Bitmap updateBitmap(String id, @Nullable Bitmap bitmap) { 54 if (bitmap == null) { 55 LottieImageAsset asset = imageAssets.get(id); 56 Bitmap ret = asset.getBitmap(); 57 asset.setBitmap(null); 58 return ret; 59 } 60 Bitmap prevBitmap = imageAssets.get(id).getBitmap(); 61 putBitmap(id, bitmap); 62 return prevBitmap; 63 } 64 getImageAssetById(String id)65 @Nullable public LottieImageAsset getImageAssetById(String id) { 66 return imageAssets.get(id); 67 } 68 bitmapForId(String id)69 @Nullable public Bitmap bitmapForId(String id) { 70 LottieImageAsset asset = imageAssets.get(id); 71 if (asset == null) { 72 return null; 73 } 74 75 Bitmap bitmap = asset.getBitmap(); 76 if (bitmap != null) { 77 return bitmap; 78 } 79 80 if (delegate != null) { 81 bitmap = delegate.fetchBitmap(asset); 82 if (bitmap != null) { 83 putBitmap(id, bitmap); 84 } 85 return bitmap; 86 } 87 88 String filename = asset.getFileName(); 89 BitmapFactory.Options opts = new BitmapFactory.Options(); 90 opts.inScaled = true; 91 opts.inDensity = 160; 92 93 if (filename.startsWith("data:") && filename.indexOf("base64,") > 0) { 94 // Contents look like a base64 data URI, with the format data:image/png;base64,<data>. 95 byte[] data; 96 try { 97 data = Base64.decode(filename.substring(filename.indexOf(',') + 1), Base64.DEFAULT); 98 } catch (IllegalArgumentException e) { 99 Logger.warning("data URL did not have correct base64 format.", e); 100 return null; 101 } 102 bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, opts); 103 return putBitmap(id, bitmap); 104 } 105 106 InputStream is; 107 try { 108 if (TextUtils.isEmpty(imagesFolder)) { 109 throw new IllegalStateException("You must set an images folder before loading an image." + 110 " Set it with LottieComposition#setImagesFolder or LottieDrawable#setImagesFolder"); 111 } 112 is = context.getAssets().open(imagesFolder + filename); 113 } catch (IOException e) { 114 Logger.warning("Unable to open asset.", e); 115 return null; 116 } 117 118 try { 119 bitmap = BitmapFactory.decodeStream(is, null, opts); 120 } catch (IllegalArgumentException e) { 121 Logger.warning("Unable to decode image.", e); 122 return null; 123 } 124 bitmap = Utils.resizeBitmapIfNeeded(bitmap, asset.getWidth(), asset.getHeight()); 125 return putBitmap(id, bitmap); 126 } 127 hasSameContext(Context context)128 public boolean hasSameContext(Context context) { 129 return context == null && this.context == null || this.context.equals(context); 130 } 131 putBitmap(String key, @Nullable Bitmap bitmap)132 private Bitmap putBitmap(String key, @Nullable Bitmap bitmap) { 133 synchronized (bitmapHashLock) { 134 imageAssets.get(key).setBitmap(bitmap); 135 return bitmap; 136 } 137 } 138 } 139