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