@amazon-devices/expo-image 2.0.1758683737
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +2 -0
- package/CHANGELOG.md +285 -0
- package/CMakeLists.txt +91 -0
- package/LICENSE +7 -0
- package/README.kepler.md +123 -0
- package/README.md +54 -0
- package/THIRD_PARTY_NOTICES.txt +24 -0
- package/android/build.gradle +124 -0
- package/android/proguard-rules.pro +28 -0
- package/android/src/main/AndroidManifest.xml +15 -0
- package/android/src/main/java/expo/modules/image/Exceptions.kt +9 -0
- package/android/src/main/java/expo/modules/image/ExpoImageAppGlideModule.kt +19 -0
- package/android/src/main/java/expo/modules/image/ExpoImageModule.kt +192 -0
- package/android/src/main/java/expo/modules/image/ExpoImageView.kt +220 -0
- package/android/src/main/java/expo/modules/image/ExpoImageViewWrapper.kt +652 -0
- package/android/src/main/java/expo/modules/image/GlideModel.kt +73 -0
- package/android/src/main/java/expo/modules/image/ImageUtils.kt +50 -0
- package/android/src/main/java/expo/modules/image/ImageViewWrapperTarget.kt +313 -0
- package/android/src/main/java/expo/modules/image/ResourceIdHelper.kt +47 -0
- package/android/src/main/java/expo/modules/image/ThumbnailRequestCoordinatorExtension.kt +21 -0
- package/android/src/main/java/expo/modules/image/YogaUtils.kt +20 -0
- package/android/src/main/java/expo/modules/image/blurhash/BlurHashFetcher.kt +32 -0
- package/android/src/main/java/expo/modules/image/blurhash/BlurhashDecoder.kt +189 -0
- package/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoader.kt +30 -0
- package/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoaderFactory.kt +14 -0
- package/android/src/main/java/expo/modules/image/blurhash/BlurhashModule.kt +17 -0
- package/android/src/main/java/expo/modules/image/dataurls/Base64DataFetcher.kt +27 -0
- package/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoader.kt +24 -0
- package/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoaderFactory.kt +12 -0
- package/android/src/main/java/expo/modules/image/dataurls/Base64Module.kt +16 -0
- package/android/src/main/java/expo/modules/image/drawing/OutlineProvider.kt +201 -0
- package/android/src/main/java/expo/modules/image/enums/ContentFit.kt +89 -0
- package/android/src/main/java/expo/modules/image/enums/ImageCacheType.kt +14 -0
- package/android/src/main/java/expo/modules/image/enums/Priority.kt +15 -0
- package/android/src/main/java/expo/modules/image/events/GlideRequestListener.kt +64 -0
- package/android/src/main/java/expo/modules/image/events/OkHttpProgressListener.kt +26 -0
- package/android/src/main/java/expo/modules/image/okhttp/ExpoImageOkHttpClientGlideModule.kt +80 -0
- package/android/src/main/java/expo/modules/image/okhttp/GlideUrlWrapperLoader.kt +55 -0
- package/android/src/main/java/expo/modules/image/records/CachePolicy.kt +10 -0
- package/android/src/main/java/expo/modules/image/records/ContentPosition.kt +89 -0
- package/android/src/main/java/expo/modules/image/records/ImageTransition.kt +8 -0
- package/android/src/main/java/expo/modules/image/records/SourceMap.kt +160 -0
- package/android/src/main/java/expo/modules/image/records/events.kt +25 -0
- package/android/src/main/java/expo/modules/image/svg/SVGDecoder.kt +33 -0
- package/android/src/main/java/expo/modules/image/svg/SVGDrawableTranscoder.kt +37 -0
- package/android/src/main/java/expo/modules/image/svg/SVGModule.kt +26 -0
- package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashDecoder.kt +365 -0
- package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashFetcher.kt +31 -0
- package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoader.kt +26 -0
- package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoaderFactory.kt +14 -0
- package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModule.kt +17 -0
- package/build/ExpoImage.d.ts +15 -0
- package/build/ExpoImage.d.ts.map +1 -0
- package/build/ExpoImage.js +75 -0
- package/build/ExpoImage.js.map +1 -0
- package/build/ExpoImage.kepler.d.ts +23 -0
- package/build/ExpoImage.kepler.d.ts.map +1 -0
- package/build/ExpoImage.kepler.js +71 -0
- package/build/ExpoImage.kepler.js.map +1 -0
- package/build/ExpoImage.web.d.ts +9 -0
- package/build/ExpoImage.web.d.ts.map +1 -0
- package/build/ExpoImage.web.js +90 -0
- package/build/ExpoImage.web.js.map +1 -0
- package/build/ExpoImageModules.d.ts +3 -0
- package/build/ExpoImageModules.d.ts.map +1 -0
- package/build/ExpoImageModules.js +4 -0
- package/build/ExpoImageModules.js.map +1 -0
- package/build/ExpoImageModules.kepler.d.ts +6 -0
- package/build/ExpoImageModules.kepler.d.ts.map +1 -0
- package/build/ExpoImageModules.kepler.js +5 -0
- package/build/ExpoImageModules.kepler.js.map +1 -0
- package/build/Image.d.ts +30 -0
- package/build/Image.d.ts.map +1 -0
- package/build/Image.js +49 -0
- package/build/Image.js.map +1 -0
- package/build/Image.types.d.ts +374 -0
- package/build/Image.types.d.ts.map +1 -0
- package/build/Image.types.js +2 -0
- package/build/Image.types.js.map +1 -0
- package/build/ImageBackground.d.ts +6 -0
- package/build/ImageBackground.d.ts.map +1 -0
- package/build/ImageBackground.js +13 -0
- package/build/ImageBackground.js.map +1 -0
- package/build/ImageViewNativeComponent.d.ts +20 -0
- package/build/ImageViewNativeComponent.d.ts.map +1 -0
- package/build/ImageViewNativeComponent.js +42 -0
- package/build/ImageViewNativeComponent.js.map +1 -0
- package/build/NativeImageLoaderKepler.d.ts +18 -0
- package/build/NativeImageLoaderKepler.d.ts.map +1 -0
- package/build/NativeImageLoaderKepler.js +3 -0
- package/build/NativeImageLoaderKepler.js.map +1 -0
- package/build/fabric/registerNativeComponent.d.ts +15 -0
- package/build/fabric/registerNativeComponent.d.ts.map +1 -0
- package/build/fabric/registerNativeComponent.js +47 -0
- package/build/fabric/registerNativeComponent.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/build/utils/AssetSourceResolver.web.d.ts +24 -0
- package/build/utils/AssetSourceResolver.web.d.ts.map +1 -0
- package/build/utils/AssetSourceResolver.web.js +67 -0
- package/build/utils/AssetSourceResolver.web.js.map +1 -0
- package/build/utils/blurhash/base83.d.ts +3 -0
- package/build/utils/blurhash/base83.d.ts.map +1 -0
- package/build/utils/blurhash/base83.js +103 -0
- package/build/utils/blurhash/base83.js.map +1 -0
- package/build/utils/blurhash/decode.d.ts +7 -0
- package/build/utils/blurhash/decode.d.ts.map +1 -0
- package/build/utils/blurhash/decode.js +92 -0
- package/build/utils/blurhash/decode.js.map +1 -0
- package/build/utils/blurhash/error.d.ts +4 -0
- package/build/utils/blurhash/error.d.ts.map +1 -0
- package/build/utils/blurhash/error.js +8 -0
- package/build/utils/blurhash/error.js.map +1 -0
- package/build/utils/blurhash/useBlurhash.d.ts +8 -0
- package/build/utils/blurhash/useBlurhash.d.ts.map +1 -0
- package/build/utils/blurhash/useBlurhash.js +64 -0
- package/build/utils/blurhash/useBlurhash.js.map +1 -0
- package/build/utils/blurhash/utils.d.ts +5 -0
- package/build/utils/blurhash/utils.d.ts.map +1 -0
- package/build/utils/blurhash/utils.js +21 -0
- package/build/utils/blurhash/utils.js.map +1 -0
- package/build/utils/resolveAssetSource.d.ts +3 -0
- package/build/utils/resolveAssetSource.d.ts.map +1 -0
- package/build/utils/resolveAssetSource.js +3 -0
- package/build/utils/resolveAssetSource.js.map +1 -0
- package/build/utils/resolveAssetSource.web.d.ts +9 -0
- package/build/utils/resolveAssetSource.web.d.ts.map +1 -0
- package/build/utils/resolveAssetSource.web.js +31 -0
- package/build/utils/resolveAssetSource.web.js.map +1 -0
- package/build/utils/resolveHashString.d.ts +14 -0
- package/build/utils/resolveHashString.d.ts.map +1 -0
- package/build/utils/resolveHashString.js +31 -0
- package/build/utils/resolveHashString.js.map +1 -0
- package/build/utils/resolveHashString.web.d.ts +20 -0
- package/build/utils/resolveHashString.web.d.ts.map +1 -0
- package/build/utils/resolveHashString.web.js +31 -0
- package/build/utils/resolveHashString.web.js.map +1 -0
- package/build/utils/resolveSources.d.ts +8 -0
- package/build/utils/resolveSources.d.ts.map +1 -0
- package/build/utils/resolveSources.js +45 -0
- package/build/utils/resolveSources.js.map +1 -0
- package/build/utils/thumbhash/thumbhash.d.ts +66 -0
- package/build/utils/thumbhash/thumbhash.d.ts.map +1 -0
- package/build/utils/thumbhash/thumbhash.js +338 -0
- package/build/utils/thumbhash/thumbhash.js.map +1 -0
- package/build/utils.d.ts +17 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +85 -0
- package/build/utils.js.map +1 -0
- package/build/web/AnimationManager.d.ts +37 -0
- package/build/web/AnimationManager.d.ts.map +1 -0
- package/build/web/AnimationManager.js +155 -0
- package/build/web/AnimationManager.js.map +1 -0
- package/build/web/ColorTintFilter.d.ts +12 -0
- package/build/web/ColorTintFilter.d.ts.map +1 -0
- package/build/web/ColorTintFilter.js +30 -0
- package/build/web/ColorTintFilter.js.map +1 -0
- package/build/web/ImageWrapper.d.ts +5 -0
- package/build/web/ImageWrapper.d.ts.map +1 -0
- package/build/web/ImageWrapper.js +44 -0
- package/build/web/ImageWrapper.js.map +1 -0
- package/build/web/ImageWrapper.types.d.ts +29 -0
- package/build/web/ImageWrapper.types.d.ts.map +1 -0
- package/build/web/ImageWrapper.types.js +2 -0
- package/build/web/ImageWrapper.types.js.map +1 -0
- package/build/web/getImageWrapperEventHandler.d.ts +8 -0
- package/build/web/getImageWrapperEventHandler.d.ts.map +1 -0
- package/build/web/getImageWrapperEventHandler.js +25 -0
- package/build/web/getImageWrapperEventHandler.js.map +1 -0
- package/build/web/hooks.d.ts +13 -0
- package/build/web/hooks.d.ts.map +1 -0
- package/build/web/hooks.js +58 -0
- package/build/web/hooks.js.map +1 -0
- package/build/web/imageStyles.d.ts +2 -0
- package/build/web/imageStyles.d.ts.map +1 -0
- package/build/web/imageStyles.js +137 -0
- package/build/web/imageStyles.js.map +1 -0
- package/build/web/positioning.d.ts +11 -0
- package/build/web/positioning.d.ts.map +1 -0
- package/build/web/positioning.js +35 -0
- package/build/web/positioning.js.map +1 -0
- package/build/web/useSourceSelection.d.ts +14 -0
- package/build/web/useSourceSelection.d.ts.map +1 -0
- package/build/web/useSourceSelection.js +98 -0
- package/build/web/useSourceSelection.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/Blurhash.swift +326 -0
- package/ios/BlurhashLoader.swift +52 -0
- package/ios/ContentFit.swift +55 -0
- package/ios/ContentPosition.swift +108 -0
- package/ios/ExpoImage.podspec +30 -0
- package/ios/ImageCachePolicy.swift +24 -0
- package/ios/ImageCacheType.swift +21 -0
- package/ios/ImageModule.swift +127 -0
- package/ios/ImagePriority.swift +22 -0
- package/ios/ImageSource.swift +44 -0
- package/ios/ImageTransition.swift +67 -0
- package/ios/ImageUtils.swift +227 -0
- package/ios/ImageView.swift +469 -0
- package/ios/Loaders/PhotoLibraryAssetLoader.swift +146 -0
- package/ios/Thumbhash.swift +550 -0
- package/ios/ThumbhashLoader.swift +53 -0
- package/jest.config.js +3 -0
- package/kepler/ExpoImage.cpp +63 -0
- package/kepler/ExpoImage.h +29 -0
- package/kepler/ExpoImageCacheHandler.cpp +15 -0
- package/kepler/ExpoImageCacheHandler.h +28 -0
- package/kepler/ExpoImageComponent.cpp +579 -0
- package/kepler/ExpoImageComponent.h +199 -0
- package/kepler/ImageWrapper/BlurhashImageWrapper.cpp +37 -0
- package/kepler/ImageWrapper/BlurhashImageWrapper.h +23 -0
- package/kepler/ImageWrapper/CacheImageWrapper.cpp +80 -0
- package/kepler/ImageWrapper/CacheImageWrapper.h +41 -0
- package/kepler/ImageWrapper/HashImageWrapper.cpp +21 -0
- package/kepler/ImageWrapper/HashImageWrapper.h +22 -0
- package/kepler/ImageWrapper/ImageWrapper.cpp +24 -0
- package/kepler/ImageWrapper/ImageWrapper.h +33 -0
- package/kepler/ImageWrapper/LocalImageWrapper.cpp +61 -0
- package/kepler/ImageWrapper/LocalImageWrapper.h +30 -0
- package/kepler/ImageWrapper/ThumbhashImageWrapper.cpp +33 -0
- package/kepler/ImageWrapper/ThumbhashImageWrapper.h +20 -0
- package/kepler/KeplerImageViewComponent.h +64 -0
- package/kepler/KeplerInit.cpp +35 -0
- package/kepler/blurhash/common.h +32 -0
- package/kepler/blurhash/decode.cpp +187 -0
- package/kepler/blurhash/decode.h +58 -0
- package/kepler/contentposition/ImageContentPosition.cpp +86 -0
- package/kepler/contentposition/ImageContentPosition.h +48 -0
- package/kepler/enums/ImageContentFit.h +17 -0
- package/kepler/enums/ImagePriority.h +17 -0
- package/kepler/managers/ExpoImageManager.cpp +38 -0
- package/kepler/managers/ExpoImageManager.h +23 -0
- package/kepler/test/ExpoImage.cpp +73 -0
- package/kepler/test/ExpoImageComponent.cpp +290 -0
- package/kepler/test/blurhash/decode.cpp +39 -0
- package/kepler/test/contentposition/ImageContentPosition.cpp +100 -0
- package/kepler/test/managers/ExpoImageManager.cpp +39 -0
- package/kepler/test/thumbhash/decode.cpp +18 -0
- package/kepler/test/utils/LoaderDelegate.cpp +130 -0
- package/kepler/thumbhash/decode.cpp +166 -0
- package/kepler/thumbhash/decode.h +22 -0
- package/kepler/utils/PlaceholderImageLoaderDelegate.cpp +26 -0
- package/kepler/utils/PlaceholderImageLoaderDelegate.h +26 -0
- package/kepler/utils/PrefetchImageLoaderDelegate.cpp +55 -0
- package/kepler/utils/PrefetchImageLoaderDelegate.h +35 -0
- package/kepler/utils/SourceImageLoaderDelegate.cpp +47 -0
- package/kepler/utils/SourceImageLoaderDelegate.h +42 -0
- package/kepler/utils/SourceImageLoaderDelegateEmitter.cpp +15 -0
- package/kepler/utils/SourceImageLoaderDelegateEmitter.h +28 -0
- package/kepler-compatibility.json +8 -0
- package/package.json +1 -0
- package/react-native.config.js +22 -0
- package/src/ExpoImage.kepler.tsx +119 -0
- package/src/ExpoImage.tsx +119 -0
- package/src/ExpoImage.web.tsx +155 -0
- package/src/ExpoImageModules.kepler.ts +7 -0
- package/src/ExpoImageModules.ts +4 -0
- package/src/Image.tsx +82 -0
- package/src/Image.types.ts +450 -0
- package/src/ImageBackground.tsx +18 -0
- package/src/ImageViewNativeComponent.ts +59 -0
- package/src/NativeImageLoaderKepler.ts +22 -0
- package/src/fabric/registerNativeComponent.ts +70 -0
- package/src/index.ts +3 -0
- package/src/utils/AssetSourceResolver.web.ts +88 -0
- package/src/utils/blurhash/base83.ts +104 -0
- package/src/utils/blurhash/decode.ts +114 -0
- package/src/utils/blurhash/error.ts +7 -0
- package/src/utils/blurhash/useBlurhash.tsx +81 -0
- package/src/utils/blurhash/utils.ts +21 -0
- package/src/utils/resolveAssetSource.tsx +3 -0
- package/src/utils/resolveAssetSource.web.ts +40 -0
- package/src/utils/resolveHashString.tsx +36 -0
- package/src/utils/resolveHashString.web.tsx +33 -0
- package/src/utils/resolveSources.tsx +48 -0
- package/src/utils/thumbhash/thumbhash.ts +387 -0
- package/src/utils.ts +109 -0
- package/src/web/AnimationManager.tsx +224 -0
- package/src/web/ColorTintFilter.tsx +36 -0
- package/src/web/ImageWrapper.tsx +85 -0
- package/src/web/ImageWrapper.types.ts +36 -0
- package/src/web/getImageWrapperEventHandler.ts +29 -0
- package/src/web/hooks.ts +72 -0
- package/src/web/imageStyles.tsx +136 -0
- package/src/web/positioning.ts +48 -0
- package/src/web/useSourceSelection.ts +151 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package expo.modules.image
|
|
2
|
+
|
|
3
|
+
import android.net.Uri
|
|
4
|
+
import com.bumptech.glide.load.model.GlideUrl
|
|
5
|
+
import expo.modules.image.okhttp.GlideUrlWrapper
|
|
6
|
+
|
|
7
|
+
sealed class GlideModel {
|
|
8
|
+
abstract val glideData: Any
|
|
9
|
+
|
|
10
|
+
override fun equals(other: Any?): Boolean {
|
|
11
|
+
if (this === other) {
|
|
12
|
+
return true
|
|
13
|
+
}
|
|
14
|
+
return other is GlideModel && glideData == other.glideData
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override fun hashCode(): Int = glideData.hashCode()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class GlideUrlModel(
|
|
21
|
+
glideUrl: GlideUrl
|
|
22
|
+
) : GlideModel() {
|
|
23
|
+
override val glideData: GlideUrlWrapper = GlideUrlWrapper(glideUrl)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class GlideRawModel(
|
|
27
|
+
data: String
|
|
28
|
+
) : GlideModel() {
|
|
29
|
+
override val glideData: String = data
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class GlideUriModel(
|
|
33
|
+
uri: Uri
|
|
34
|
+
) : GlideModel() {
|
|
35
|
+
override val glideData: Uri = uri
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class GlideBlurhashModel(
|
|
39
|
+
val uri: Uri,
|
|
40
|
+
val width: Int,
|
|
41
|
+
val height: Int
|
|
42
|
+
) : GlideModel() {
|
|
43
|
+
override val glideData: GlideBlurhashModel = this
|
|
44
|
+
|
|
45
|
+
override fun equals(other: Any?): Boolean {
|
|
46
|
+
if (this === other) {
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return other is GlideBlurhashModel && uri == other.uri && width == other.width && height == other.height
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun hashCode(): Int {
|
|
54
|
+
var result = uri.hashCode()
|
|
55
|
+
result = 31 * result + width
|
|
56
|
+
result = 31 * result + height
|
|
57
|
+
return result
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class GlideThumbhashModel(
|
|
62
|
+
var uri: Uri
|
|
63
|
+
) : GlideModel() {
|
|
64
|
+
override val glideData: GlideThumbhashModel = this
|
|
65
|
+
|
|
66
|
+
override fun equals(other: Any?): Boolean {
|
|
67
|
+
return (this === other) || other is GlideThumbhashModel && uri == other.uri
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override fun hashCode(): Int {
|
|
71
|
+
return uri.hashCode()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
package expo.modules.image
|
|
2
|
+
|
|
3
|
+
import android.graphics.RectF
|
|
4
|
+
import com.bumptech.glide.request.FutureTarget
|
|
5
|
+
import kotlinx.coroutines.Dispatchers
|
|
6
|
+
import kotlinx.coroutines.runInterruptible
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Converts blocking [java.util.concurrent.Future] result into non-blocking suspend function.
|
|
10
|
+
*/
|
|
11
|
+
internal suspend fun <T> FutureTarget<T>.awaitGet(): T = runInterruptible(Dispatchers.IO) { get() }
|
|
12
|
+
|
|
13
|
+
fun calcXTranslation(
|
|
14
|
+
value: Float,
|
|
15
|
+
imageRect: RectF,
|
|
16
|
+
viewRect: RectF,
|
|
17
|
+
isPercentage: Boolean = false,
|
|
18
|
+
isReverse: Boolean = false
|
|
19
|
+
): Float = calcTranslation(value, imageRect.width(), viewRect.width(), isPercentage, isReverse)
|
|
20
|
+
|
|
21
|
+
fun calcYTranslation(
|
|
22
|
+
value: Float,
|
|
23
|
+
imageRect: RectF,
|
|
24
|
+
viewRect: RectF,
|
|
25
|
+
isPercentage: Boolean = false,
|
|
26
|
+
isReverse: Boolean = false
|
|
27
|
+
): Float = calcTranslation(value, imageRect.height(), viewRect.height(), isPercentage, isReverse)
|
|
28
|
+
|
|
29
|
+
fun calcTranslation(
|
|
30
|
+
value: Float,
|
|
31
|
+
imageRefValue: Float,
|
|
32
|
+
viewRefValue: Float,
|
|
33
|
+
isPercentage: Boolean = false,
|
|
34
|
+
isReverse: Boolean = false
|
|
35
|
+
): Float {
|
|
36
|
+
if (isPercentage) {
|
|
37
|
+
val finalPercentage = if (isReverse) {
|
|
38
|
+
100f - value
|
|
39
|
+
} else {
|
|
40
|
+
value
|
|
41
|
+
}
|
|
42
|
+
return (finalPercentage / 100f) * (viewRefValue - imageRefValue)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (isReverse) {
|
|
46
|
+
return viewRefValue - imageRefValue - value
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return value
|
|
50
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
package expo.modules.image
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Point
|
|
5
|
+
import android.graphics.drawable.Drawable
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import android.view.ViewGroup
|
|
8
|
+
import android.view.ViewTreeObserver
|
|
9
|
+
import android.view.WindowManager
|
|
10
|
+
import androidx.annotation.VisibleForTesting
|
|
11
|
+
import com.bumptech.glide.RequestManager
|
|
12
|
+
import com.bumptech.glide.request.Request
|
|
13
|
+
import com.bumptech.glide.request.ThumbnailRequestCoordinator
|
|
14
|
+
import com.bumptech.glide.request.target.SizeReadyCallback
|
|
15
|
+
import com.bumptech.glide.request.target.Target
|
|
16
|
+
import com.bumptech.glide.request.transition.Transition
|
|
17
|
+
import com.bumptech.glide.util.Preconditions
|
|
18
|
+
import com.bumptech.glide.util.Synthetic
|
|
19
|
+
import expo.modules.core.utilities.ifNull
|
|
20
|
+
import expo.modules.image.enums.ContentFit
|
|
21
|
+
import java.lang.ref.WeakReference
|
|
22
|
+
import kotlin.math.max
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A custom target to provide a smooth transition between multiple drawables.
|
|
26
|
+
* It delegates images to the [ExpoImageViewWrapper], where we handle the loaded [Drawable].
|
|
27
|
+
* When the target is cleared, we don't do anything. The [ExpoImageViewWrapper] is responsible for
|
|
28
|
+
* clearing bitmaps before freeing targets. That may be error-prone, but that is the only way
|
|
29
|
+
* of implementing the transition between bitmaps.
|
|
30
|
+
*/
|
|
31
|
+
class ImageViewWrapperTarget(
|
|
32
|
+
private val imageViewHolder: WeakReference<ExpoImageViewWrapper>,
|
|
33
|
+
) : Target<Drawable> {
|
|
34
|
+
/**
|
|
35
|
+
* Whether the target has a main, non-placeholder source
|
|
36
|
+
*/
|
|
37
|
+
var hasSource = false
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether the target is used - the asset loaded by it has been drawn in the image view
|
|
41
|
+
*/
|
|
42
|
+
var isUsed = false
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The main source height where -1 means unknown
|
|
46
|
+
*/
|
|
47
|
+
var sourceHeight = -1
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The main source width where -1 means unknown
|
|
51
|
+
*/
|
|
52
|
+
var sourceWidth = -1
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The content fit of the placeholder
|
|
56
|
+
*/
|
|
57
|
+
var placeholderContentFit: ContentFit? = null
|
|
58
|
+
|
|
59
|
+
private var request: Request? = null
|
|
60
|
+
private var sizeDeterminer = SizeDeterminer(imageViewHolder)
|
|
61
|
+
|
|
62
|
+
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
|
63
|
+
// The image view should always be valid. When the view is deallocated, all targets should be
|
|
64
|
+
// canceled. Therefore that code shouldn't be called in that case. Instead of crashing, we
|
|
65
|
+
// decided to ignore that.
|
|
66
|
+
val imageView = imageViewHolder.get().ifNull {
|
|
67
|
+
Log.w("ExpoImage", "The `ExpoImageViewWrapper` was deallocated, but the target wasn't canceled in time.")
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// The thumbnail and full request are handled in the same way by Glide.
|
|
72
|
+
// Here we're checking if the provided resource is the final bitmap or a thumbnail.
|
|
73
|
+
val isPlaceholder = if (request is ThumbnailRequestCoordinator) {
|
|
74
|
+
(request as? ThumbnailRequestCoordinator)
|
|
75
|
+
?.getPrivateFullRequest()
|
|
76
|
+
?.isComplete == false
|
|
77
|
+
} else {
|
|
78
|
+
false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
imageView.onResourceReady(this, resource, isPlaceholder)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
override fun onStart() = Unit
|
|
85
|
+
|
|
86
|
+
override fun onStop() = Unit
|
|
87
|
+
|
|
88
|
+
override fun onDestroy() = Unit
|
|
89
|
+
|
|
90
|
+
override fun onLoadStarted(placeholder: Drawable?) = Unit
|
|
91
|
+
|
|
92
|
+
// When loading fails, it's handled by the global listener, therefore that method can be NOOP.
|
|
93
|
+
override fun onLoadFailed(errorDrawable: Drawable?) = Unit
|
|
94
|
+
|
|
95
|
+
override fun onLoadCleared(placeholder: Drawable?) = Unit
|
|
96
|
+
|
|
97
|
+
override fun getSize(cb: SizeReadyCallback) {
|
|
98
|
+
// If we can't resolve the image, we just return unknown size.
|
|
99
|
+
// It shouldn't happen in a production application, because it means that our view was deallocated.
|
|
100
|
+
if (imageViewHolder.get() == null) {
|
|
101
|
+
cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
sizeDeterminer.getSize(cb)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
override fun removeCallback(cb: SizeReadyCallback) {
|
|
109
|
+
sizeDeterminer.removeCallback(cb)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
override fun setRequest(request: Request?) {
|
|
113
|
+
this.request = request
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
override fun getRequest() = request
|
|
117
|
+
|
|
118
|
+
fun clear(requestManager: RequestManager) {
|
|
119
|
+
sizeDeterminer.clearCallbacksAndListener()
|
|
120
|
+
requestManager.clear(this)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Copied from the Glide codebase.
|
|
125
|
+
// We modified that to receive a weak ref to our view instead of strong one.
|
|
126
|
+
internal class SizeDeterminer(private val imageViewHolder: WeakReference<ExpoImageViewWrapper>) {
|
|
127
|
+
private val cbs: MutableList<SizeReadyCallback> = ArrayList()
|
|
128
|
+
|
|
129
|
+
@Synthetic
|
|
130
|
+
var waitForLayout = false
|
|
131
|
+
private var layoutListener: SizeDeterminerLayoutListener? = null
|
|
132
|
+
private fun notifyCbs(width: Int, height: Int) {
|
|
133
|
+
// One or more callbacks may trigger the removal of one or more additional callbacks, so we
|
|
134
|
+
// need a copy of the list to avoid a concurrent modification exception. One place this
|
|
135
|
+
// happens is when a full request completes from the in memory cache while its thumbnail is
|
|
136
|
+
// still being loaded asynchronously. See #2237.
|
|
137
|
+
for (cb in ArrayList(cbs)) {
|
|
138
|
+
cb.onSizeReady(width, height)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@Synthetic
|
|
143
|
+
fun checkCurrentDimens() {
|
|
144
|
+
if (cbs.isEmpty()) {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
val currentWidth = targetWidth
|
|
148
|
+
val currentHeight = targetHeight
|
|
149
|
+
if (!isViewStateAndSizeValid(currentWidth, currentHeight)) {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
notifyCbs(currentWidth, currentHeight)
|
|
153
|
+
clearCallbacksAndListener()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fun getSize(cb: SizeReadyCallback) {
|
|
157
|
+
val view = imageViewHolder.get() ?: return
|
|
158
|
+
|
|
159
|
+
val currentWidth = targetWidth
|
|
160
|
+
val currentHeight = targetHeight
|
|
161
|
+
if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
|
|
162
|
+
cb.onSizeReady(currentWidth, currentHeight)
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// We want to notify callbacks in the order they were added and we only expect one or two
|
|
167
|
+
// callbacks to be added a time, so a List is a reasonable choice.
|
|
168
|
+
if (!cbs.contains(cb)) {
|
|
169
|
+
cbs.add(cb)
|
|
170
|
+
}
|
|
171
|
+
if (layoutListener == null) {
|
|
172
|
+
val observer = view.viewTreeObserver
|
|
173
|
+
layoutListener = SizeDeterminerLayoutListener(this)
|
|
174
|
+
observer.addOnPreDrawListener(layoutListener)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The callback may be called anyway if it is removed by another [SizeReadyCallback] or
|
|
180
|
+
* otherwise removed while we're notifying the list of callbacks.
|
|
181
|
+
*
|
|
182
|
+
*
|
|
183
|
+
* See #2237.
|
|
184
|
+
*/
|
|
185
|
+
fun removeCallback(cb: SizeReadyCallback) {
|
|
186
|
+
cbs.remove(cb)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
fun clearCallbacksAndListener() {
|
|
190
|
+
// Keep a reference to the layout attachStateListener and remove it here
|
|
191
|
+
// rather than having the observer remove itself because the observer
|
|
192
|
+
// we add the attachStateListener to will be almost immediately merged into
|
|
193
|
+
// another observer and will therefore never be alive. If we instead
|
|
194
|
+
// keep a reference to the attachStateListener and remove it here, we get the
|
|
195
|
+
// current view tree observer and should succeed.
|
|
196
|
+
val observer = imageViewHolder.get()?.viewTreeObserver
|
|
197
|
+
if (observer?.isAlive == true) {
|
|
198
|
+
observer.removeOnPreDrawListener(layoutListener)
|
|
199
|
+
}
|
|
200
|
+
layoutListener = null
|
|
201
|
+
cbs.clear()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private fun isViewStateAndSizeValid(width: Int, height: Int): Boolean {
|
|
205
|
+
return isDimensionValid(width) && isDimensionValid(height)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private val targetHeight: Int
|
|
209
|
+
get() {
|
|
210
|
+
val view = imageViewHolder.get() ?: return Target.SIZE_ORIGINAL
|
|
211
|
+
val verticalPadding = view.paddingTop + view.paddingBottom
|
|
212
|
+
val layoutParams = view.layoutParams
|
|
213
|
+
val layoutParamSize = layoutParams?.height ?: PENDING_SIZE
|
|
214
|
+
return getTargetDimen(view.height, layoutParamSize, verticalPadding)
|
|
215
|
+
}
|
|
216
|
+
private val targetWidth: Int
|
|
217
|
+
get() {
|
|
218
|
+
val view = imageViewHolder.get() ?: return Target.SIZE_ORIGINAL
|
|
219
|
+
val horizontalPadding = view.paddingLeft + view.paddingRight
|
|
220
|
+
val layoutParams = view.layoutParams
|
|
221
|
+
val layoutParamSize = layoutParams?.width ?: PENDING_SIZE
|
|
222
|
+
return getTargetDimen(view.width, layoutParamSize, horizontalPadding)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private fun getTargetDimen(viewSize: Int, paramSize: Int, paddingSize: Int): Int {
|
|
226
|
+
val view = imageViewHolder.get() ?: return Target.SIZE_ORIGINAL
|
|
227
|
+
|
|
228
|
+
// We consider the View state as valid if the View has non-null layout params and a non-zero
|
|
229
|
+
// layout params width and height. This is imperfect. We're making an assumption that View
|
|
230
|
+
// parents will obey their child's layout parameters, which isn't always the case.
|
|
231
|
+
val adjustedParamSize = paramSize - paddingSize
|
|
232
|
+
if (adjustedParamSize > 0) {
|
|
233
|
+
return adjustedParamSize
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Since we always prefer layout parameters with fixed sizes, even if waitForLayout is true,
|
|
237
|
+
// we might as well ignore it and just return the layout parameters above if we have them.
|
|
238
|
+
// Otherwise we should wait for a layout pass before checking the View's dimensions.
|
|
239
|
+
if (waitForLayout && view.isLayoutRequested) {
|
|
240
|
+
return PENDING_SIZE
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// We also consider the View state valid if the View has a non-zero width and height. This
|
|
244
|
+
// means that the View has gone through at least one layout pass. It does not mean the Views
|
|
245
|
+
// width and height are from the current layout pass. For example, if a View is re-used in
|
|
246
|
+
// RecyclerView or ListView, this width/height may be from an old position. In some cases
|
|
247
|
+
// the dimensions of the View at the old position may be different than the dimensions of the
|
|
248
|
+
// View in the new position because the LayoutManager/ViewParent can arbitrarily decide to
|
|
249
|
+
// change them. Nevertheless, in most cases this should be a reasonable choice.
|
|
250
|
+
val adjustedViewSize = viewSize - paddingSize
|
|
251
|
+
if (adjustedViewSize > 0) {
|
|
252
|
+
return adjustedViewSize
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Finally we consider the view valid if the layout parameter size is set to wrap_content.
|
|
256
|
+
// It's difficult for Glide to figure out what to do here. Although Target.SIZE_ORIGINAL is a
|
|
257
|
+
// coherent choice, it's extremely dangerous because original images may be much too large to
|
|
258
|
+
// fit in memory or so large that only a couple can fit in memory, causing OOMs. If users want
|
|
259
|
+
// the original image, they can always use .override(Target.SIZE_ORIGINAL). Since wrap_content
|
|
260
|
+
// may never resolve to a real size unless we load something, we aim for a square whose length
|
|
261
|
+
// is the largest screen size. That way we're loading something and that something has some
|
|
262
|
+
// hope of being downsampled to a size that the device can support. We also log a warning that
|
|
263
|
+
// tries to explain what Glide is doing and why some alternatives are preferable.
|
|
264
|
+
// Since WRAP_CONTENT is sometimes used as a default layout parameter, we always wait for
|
|
265
|
+
// layout to complete before using this fallback parameter (ConstraintLayout among others).
|
|
266
|
+
if (!view.isLayoutRequested && paramSize == ViewGroup.LayoutParams.WRAP_CONTENT) {
|
|
267
|
+
return getMaxDisplayLength(view.context)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// If the layout parameters are < padding, the view size is < padding, or the layout
|
|
271
|
+
// parameters are set to match_parent or wrap_content and no layout has occurred, we should
|
|
272
|
+
// wait for layout and repeat.
|
|
273
|
+
return PENDING_SIZE
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private fun isDimensionValid(size: Int): Boolean {
|
|
277
|
+
return size > 0 || size == Target.SIZE_ORIGINAL
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private class SizeDeterminerLayoutListener(sizeDeterminer: SizeDeterminer) : ViewTreeObserver.OnPreDrawListener {
|
|
281
|
+
private val sizeDeterminerRef: WeakReference<SizeDeterminer>
|
|
282
|
+
|
|
283
|
+
init {
|
|
284
|
+
sizeDeterminerRef = WeakReference(sizeDeterminer)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
override fun onPreDraw(): Boolean {
|
|
288
|
+
val sizeDeterminer = sizeDeterminerRef.get()
|
|
289
|
+
sizeDeterminer?.checkCurrentDimens()
|
|
290
|
+
return true
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
companion object {
|
|
295
|
+
// Some negative sizes (Target.SIZE_ORIGINAL) are valid, 0 is never valid.
|
|
296
|
+
private const val PENDING_SIZE = 0
|
|
297
|
+
|
|
298
|
+
@VisibleForTesting
|
|
299
|
+
var maxDisplayLength: Int? = null
|
|
300
|
+
|
|
301
|
+
// Use the maximum to avoid depending on the device's current orientation.
|
|
302
|
+
private fun getMaxDisplayLength(context: Context): Int {
|
|
303
|
+
if (maxDisplayLength == null) {
|
|
304
|
+
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
|
305
|
+
val display = Preconditions.checkNotNull(windowManager).defaultDisplay
|
|
306
|
+
val displayDimensions = Point()
|
|
307
|
+
display.getSize(displayDimensions)
|
|
308
|
+
maxDisplayLength = max(displayDimensions.x, displayDimensions.y)
|
|
309
|
+
}
|
|
310
|
+
return maxDisplayLength!!
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package expo.modules.image
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper
|
|
7
|
+
import java.util.*
|
|
8
|
+
|
|
9
|
+
object ResourceIdHelper {
|
|
10
|
+
private val idMap = mutableMapOf<String, Int>()
|
|
11
|
+
|
|
12
|
+
@SuppressLint("DiscouragedApi")
|
|
13
|
+
private fun getResourceRawId(context: Context, name: String): Int {
|
|
14
|
+
if (name.isEmpty()) {
|
|
15
|
+
return -1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
val normalizedName = name.lowercase(Locale.ROOT).replace("-", "_")
|
|
19
|
+
synchronized(this) {
|
|
20
|
+
val id = idMap[normalizedName]
|
|
21
|
+
if (id != null) {
|
|
22
|
+
return id
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return context
|
|
26
|
+
.resources
|
|
27
|
+
.getIdentifier(normalizedName, "raw", context.packageName)
|
|
28
|
+
.also {
|
|
29
|
+
idMap[normalizedName] = it
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fun getResourceUri(context: Context, name: String): Uri? {
|
|
35
|
+
val drawableUri = ResourceDrawableIdHelper.getInstance().getResourceDrawableUri(context, name)
|
|
36
|
+
if (drawableUri != null && drawableUri != Uri.EMPTY) {
|
|
37
|
+
return drawableUri
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
val resId = getResourceRawId(context, name)
|
|
41
|
+
return if (resId > 0) {
|
|
42
|
+
Uri.Builder().scheme("res").path(resId.toString()).build()
|
|
43
|
+
} else {
|
|
44
|
+
null
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
package expo.modules.image
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.bumptech.glide.request.Request
|
|
5
|
+
import com.bumptech.glide.request.ThumbnailRequestCoordinator
|
|
6
|
+
|
|
7
|
+
fun ThumbnailRequestCoordinator.getPrivateFullRequest(): Request? {
|
|
8
|
+
return getPrivateField("full")
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
private fun <T> ThumbnailRequestCoordinator.getPrivateField(name: String): T? {
|
|
12
|
+
return try {
|
|
13
|
+
val field = this.javaClass.getDeclaredField(name)
|
|
14
|
+
field.isAccessible = true
|
|
15
|
+
@Suppress("UNCHECKED_CAST")
|
|
16
|
+
field.get(this) as T
|
|
17
|
+
} catch (e: Throwable) {
|
|
18
|
+
Log.e("ExpoImage", "Couldn't receive the `$name` field", e)
|
|
19
|
+
null
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package expo.modules.image
|
|
2
|
+
|
|
3
|
+
import com.facebook.yoga.YogaConstants
|
|
4
|
+
|
|
5
|
+
fun Float.ifYogaUndefinedUse(value: Float) =
|
|
6
|
+
if (YogaConstants.isUndefined(this)) {
|
|
7
|
+
value
|
|
8
|
+
} else {
|
|
9
|
+
this
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
inline fun Float.ifYogaDefinedUse(transformFun: (current: Float) -> Float) =
|
|
13
|
+
if (YogaConstants.isUndefined(this)) {
|
|
14
|
+
this
|
|
15
|
+
} else {
|
|
16
|
+
transformFun(this)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
fun makeYogaUndefinedIfNegative(value: Float) =
|
|
20
|
+
if (!YogaConstants.isUndefined(value) && value < 0) YogaConstants.UNDEFINED else value
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package expo.modules.image.blurhash
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import com.bumptech.glide.Priority
|
|
5
|
+
import com.bumptech.glide.load.DataSource
|
|
6
|
+
import com.bumptech.glide.load.data.DataFetcher
|
|
7
|
+
import expo.modules.kotlin.exception.CodedException
|
|
8
|
+
|
|
9
|
+
class BlurhashDecodingFailure(blurHash: String?) : CodedException(
|
|
10
|
+
message = "Cannot decode provided blurhash '$blurHash'"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
class BlurHashFetcher(
|
|
14
|
+
private val blurHash: String?,
|
|
15
|
+
private val width: Int,
|
|
16
|
+
private val height: Int,
|
|
17
|
+
private val punch: Float
|
|
18
|
+
) : DataFetcher<Bitmap> {
|
|
19
|
+
override fun cleanup() = Unit
|
|
20
|
+
override fun cancel() = Unit
|
|
21
|
+
override fun getDataClass(): Class<Bitmap> = Bitmap::class.java
|
|
22
|
+
override fun getDataSource(): DataSource = DataSource.LOCAL
|
|
23
|
+
|
|
24
|
+
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in Bitmap>) {
|
|
25
|
+
val bitmap = BlurhashDecoder.decode(blurHash, width, height, punch)
|
|
26
|
+
if (bitmap == null) {
|
|
27
|
+
callback.onLoadFailed(BlurhashDecodingFailure(blurHash))
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
callback.onDataReady(bitmap)
|
|
31
|
+
}
|
|
32
|
+
}
|