@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.
Files changed (289) hide show
  1. package/.eslintrc.js +2 -0
  2. package/CHANGELOG.md +285 -0
  3. package/CMakeLists.txt +91 -0
  4. package/LICENSE +7 -0
  5. package/README.kepler.md +123 -0
  6. package/README.md +54 -0
  7. package/THIRD_PARTY_NOTICES.txt +24 -0
  8. package/android/build.gradle +124 -0
  9. package/android/proguard-rules.pro +28 -0
  10. package/android/src/main/AndroidManifest.xml +15 -0
  11. package/android/src/main/java/expo/modules/image/Exceptions.kt +9 -0
  12. package/android/src/main/java/expo/modules/image/ExpoImageAppGlideModule.kt +19 -0
  13. package/android/src/main/java/expo/modules/image/ExpoImageModule.kt +192 -0
  14. package/android/src/main/java/expo/modules/image/ExpoImageView.kt +220 -0
  15. package/android/src/main/java/expo/modules/image/ExpoImageViewWrapper.kt +652 -0
  16. package/android/src/main/java/expo/modules/image/GlideModel.kt +73 -0
  17. package/android/src/main/java/expo/modules/image/ImageUtils.kt +50 -0
  18. package/android/src/main/java/expo/modules/image/ImageViewWrapperTarget.kt +313 -0
  19. package/android/src/main/java/expo/modules/image/ResourceIdHelper.kt +47 -0
  20. package/android/src/main/java/expo/modules/image/ThumbnailRequestCoordinatorExtension.kt +21 -0
  21. package/android/src/main/java/expo/modules/image/YogaUtils.kt +20 -0
  22. package/android/src/main/java/expo/modules/image/blurhash/BlurHashFetcher.kt +32 -0
  23. package/android/src/main/java/expo/modules/image/blurhash/BlurhashDecoder.kt +189 -0
  24. package/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoader.kt +30 -0
  25. package/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoaderFactory.kt +14 -0
  26. package/android/src/main/java/expo/modules/image/blurhash/BlurhashModule.kt +17 -0
  27. package/android/src/main/java/expo/modules/image/dataurls/Base64DataFetcher.kt +27 -0
  28. package/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoader.kt +24 -0
  29. package/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoaderFactory.kt +12 -0
  30. package/android/src/main/java/expo/modules/image/dataurls/Base64Module.kt +16 -0
  31. package/android/src/main/java/expo/modules/image/drawing/OutlineProvider.kt +201 -0
  32. package/android/src/main/java/expo/modules/image/enums/ContentFit.kt +89 -0
  33. package/android/src/main/java/expo/modules/image/enums/ImageCacheType.kt +14 -0
  34. package/android/src/main/java/expo/modules/image/enums/Priority.kt +15 -0
  35. package/android/src/main/java/expo/modules/image/events/GlideRequestListener.kt +64 -0
  36. package/android/src/main/java/expo/modules/image/events/OkHttpProgressListener.kt +26 -0
  37. package/android/src/main/java/expo/modules/image/okhttp/ExpoImageOkHttpClientGlideModule.kt +80 -0
  38. package/android/src/main/java/expo/modules/image/okhttp/GlideUrlWrapperLoader.kt +55 -0
  39. package/android/src/main/java/expo/modules/image/records/CachePolicy.kt +10 -0
  40. package/android/src/main/java/expo/modules/image/records/ContentPosition.kt +89 -0
  41. package/android/src/main/java/expo/modules/image/records/ImageTransition.kt +8 -0
  42. package/android/src/main/java/expo/modules/image/records/SourceMap.kt +160 -0
  43. package/android/src/main/java/expo/modules/image/records/events.kt +25 -0
  44. package/android/src/main/java/expo/modules/image/svg/SVGDecoder.kt +33 -0
  45. package/android/src/main/java/expo/modules/image/svg/SVGDrawableTranscoder.kt +37 -0
  46. package/android/src/main/java/expo/modules/image/svg/SVGModule.kt +26 -0
  47. package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashDecoder.kt +365 -0
  48. package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashFetcher.kt +31 -0
  49. package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoader.kt +26 -0
  50. package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoaderFactory.kt +14 -0
  51. package/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModule.kt +17 -0
  52. package/build/ExpoImage.d.ts +15 -0
  53. package/build/ExpoImage.d.ts.map +1 -0
  54. package/build/ExpoImage.js +75 -0
  55. package/build/ExpoImage.js.map +1 -0
  56. package/build/ExpoImage.kepler.d.ts +23 -0
  57. package/build/ExpoImage.kepler.d.ts.map +1 -0
  58. package/build/ExpoImage.kepler.js +71 -0
  59. package/build/ExpoImage.kepler.js.map +1 -0
  60. package/build/ExpoImage.web.d.ts +9 -0
  61. package/build/ExpoImage.web.d.ts.map +1 -0
  62. package/build/ExpoImage.web.js +90 -0
  63. package/build/ExpoImage.web.js.map +1 -0
  64. package/build/ExpoImageModules.d.ts +3 -0
  65. package/build/ExpoImageModules.d.ts.map +1 -0
  66. package/build/ExpoImageModules.js +4 -0
  67. package/build/ExpoImageModules.js.map +1 -0
  68. package/build/ExpoImageModules.kepler.d.ts +6 -0
  69. package/build/ExpoImageModules.kepler.d.ts.map +1 -0
  70. package/build/ExpoImageModules.kepler.js +5 -0
  71. package/build/ExpoImageModules.kepler.js.map +1 -0
  72. package/build/Image.d.ts +30 -0
  73. package/build/Image.d.ts.map +1 -0
  74. package/build/Image.js +49 -0
  75. package/build/Image.js.map +1 -0
  76. package/build/Image.types.d.ts +374 -0
  77. package/build/Image.types.d.ts.map +1 -0
  78. package/build/Image.types.js +2 -0
  79. package/build/Image.types.js.map +1 -0
  80. package/build/ImageBackground.d.ts +6 -0
  81. package/build/ImageBackground.d.ts.map +1 -0
  82. package/build/ImageBackground.js +13 -0
  83. package/build/ImageBackground.js.map +1 -0
  84. package/build/ImageViewNativeComponent.d.ts +20 -0
  85. package/build/ImageViewNativeComponent.d.ts.map +1 -0
  86. package/build/ImageViewNativeComponent.js +42 -0
  87. package/build/ImageViewNativeComponent.js.map +1 -0
  88. package/build/NativeImageLoaderKepler.d.ts +18 -0
  89. package/build/NativeImageLoaderKepler.d.ts.map +1 -0
  90. package/build/NativeImageLoaderKepler.js +3 -0
  91. package/build/NativeImageLoaderKepler.js.map +1 -0
  92. package/build/fabric/registerNativeComponent.d.ts +15 -0
  93. package/build/fabric/registerNativeComponent.d.ts.map +1 -0
  94. package/build/fabric/registerNativeComponent.js +47 -0
  95. package/build/fabric/registerNativeComponent.js.map +1 -0
  96. package/build/index.d.ts +4 -0
  97. package/build/index.d.ts.map +1 -0
  98. package/build/index.js +4 -0
  99. package/build/index.js.map +1 -0
  100. package/build/utils/AssetSourceResolver.web.d.ts +24 -0
  101. package/build/utils/AssetSourceResolver.web.d.ts.map +1 -0
  102. package/build/utils/AssetSourceResolver.web.js +67 -0
  103. package/build/utils/AssetSourceResolver.web.js.map +1 -0
  104. package/build/utils/blurhash/base83.d.ts +3 -0
  105. package/build/utils/blurhash/base83.d.ts.map +1 -0
  106. package/build/utils/blurhash/base83.js +103 -0
  107. package/build/utils/blurhash/base83.js.map +1 -0
  108. package/build/utils/blurhash/decode.d.ts +7 -0
  109. package/build/utils/blurhash/decode.d.ts.map +1 -0
  110. package/build/utils/blurhash/decode.js +92 -0
  111. package/build/utils/blurhash/decode.js.map +1 -0
  112. package/build/utils/blurhash/error.d.ts +4 -0
  113. package/build/utils/blurhash/error.d.ts.map +1 -0
  114. package/build/utils/blurhash/error.js +8 -0
  115. package/build/utils/blurhash/error.js.map +1 -0
  116. package/build/utils/blurhash/useBlurhash.d.ts +8 -0
  117. package/build/utils/blurhash/useBlurhash.d.ts.map +1 -0
  118. package/build/utils/blurhash/useBlurhash.js +64 -0
  119. package/build/utils/blurhash/useBlurhash.js.map +1 -0
  120. package/build/utils/blurhash/utils.d.ts +5 -0
  121. package/build/utils/blurhash/utils.d.ts.map +1 -0
  122. package/build/utils/blurhash/utils.js +21 -0
  123. package/build/utils/blurhash/utils.js.map +1 -0
  124. package/build/utils/resolveAssetSource.d.ts +3 -0
  125. package/build/utils/resolveAssetSource.d.ts.map +1 -0
  126. package/build/utils/resolveAssetSource.js +3 -0
  127. package/build/utils/resolveAssetSource.js.map +1 -0
  128. package/build/utils/resolveAssetSource.web.d.ts +9 -0
  129. package/build/utils/resolveAssetSource.web.d.ts.map +1 -0
  130. package/build/utils/resolveAssetSource.web.js +31 -0
  131. package/build/utils/resolveAssetSource.web.js.map +1 -0
  132. package/build/utils/resolveHashString.d.ts +14 -0
  133. package/build/utils/resolveHashString.d.ts.map +1 -0
  134. package/build/utils/resolveHashString.js +31 -0
  135. package/build/utils/resolveHashString.js.map +1 -0
  136. package/build/utils/resolveHashString.web.d.ts +20 -0
  137. package/build/utils/resolveHashString.web.d.ts.map +1 -0
  138. package/build/utils/resolveHashString.web.js +31 -0
  139. package/build/utils/resolveHashString.web.js.map +1 -0
  140. package/build/utils/resolveSources.d.ts +8 -0
  141. package/build/utils/resolveSources.d.ts.map +1 -0
  142. package/build/utils/resolveSources.js +45 -0
  143. package/build/utils/resolveSources.js.map +1 -0
  144. package/build/utils/thumbhash/thumbhash.d.ts +66 -0
  145. package/build/utils/thumbhash/thumbhash.d.ts.map +1 -0
  146. package/build/utils/thumbhash/thumbhash.js +338 -0
  147. package/build/utils/thumbhash/thumbhash.js.map +1 -0
  148. package/build/utils.d.ts +17 -0
  149. package/build/utils.d.ts.map +1 -0
  150. package/build/utils.js +85 -0
  151. package/build/utils.js.map +1 -0
  152. package/build/web/AnimationManager.d.ts +37 -0
  153. package/build/web/AnimationManager.d.ts.map +1 -0
  154. package/build/web/AnimationManager.js +155 -0
  155. package/build/web/AnimationManager.js.map +1 -0
  156. package/build/web/ColorTintFilter.d.ts +12 -0
  157. package/build/web/ColorTintFilter.d.ts.map +1 -0
  158. package/build/web/ColorTintFilter.js +30 -0
  159. package/build/web/ColorTintFilter.js.map +1 -0
  160. package/build/web/ImageWrapper.d.ts +5 -0
  161. package/build/web/ImageWrapper.d.ts.map +1 -0
  162. package/build/web/ImageWrapper.js +44 -0
  163. package/build/web/ImageWrapper.js.map +1 -0
  164. package/build/web/ImageWrapper.types.d.ts +29 -0
  165. package/build/web/ImageWrapper.types.d.ts.map +1 -0
  166. package/build/web/ImageWrapper.types.js +2 -0
  167. package/build/web/ImageWrapper.types.js.map +1 -0
  168. package/build/web/getImageWrapperEventHandler.d.ts +8 -0
  169. package/build/web/getImageWrapperEventHandler.d.ts.map +1 -0
  170. package/build/web/getImageWrapperEventHandler.js +25 -0
  171. package/build/web/getImageWrapperEventHandler.js.map +1 -0
  172. package/build/web/hooks.d.ts +13 -0
  173. package/build/web/hooks.d.ts.map +1 -0
  174. package/build/web/hooks.js +58 -0
  175. package/build/web/hooks.js.map +1 -0
  176. package/build/web/imageStyles.d.ts +2 -0
  177. package/build/web/imageStyles.d.ts.map +1 -0
  178. package/build/web/imageStyles.js +137 -0
  179. package/build/web/imageStyles.js.map +1 -0
  180. package/build/web/positioning.d.ts +11 -0
  181. package/build/web/positioning.d.ts.map +1 -0
  182. package/build/web/positioning.js +35 -0
  183. package/build/web/positioning.js.map +1 -0
  184. package/build/web/useSourceSelection.d.ts +14 -0
  185. package/build/web/useSourceSelection.d.ts.map +1 -0
  186. package/build/web/useSourceSelection.js +98 -0
  187. package/build/web/useSourceSelection.js.map +1 -0
  188. package/expo-module.config.json +9 -0
  189. package/ios/Blurhash.swift +326 -0
  190. package/ios/BlurhashLoader.swift +52 -0
  191. package/ios/ContentFit.swift +55 -0
  192. package/ios/ContentPosition.swift +108 -0
  193. package/ios/ExpoImage.podspec +30 -0
  194. package/ios/ImageCachePolicy.swift +24 -0
  195. package/ios/ImageCacheType.swift +21 -0
  196. package/ios/ImageModule.swift +127 -0
  197. package/ios/ImagePriority.swift +22 -0
  198. package/ios/ImageSource.swift +44 -0
  199. package/ios/ImageTransition.swift +67 -0
  200. package/ios/ImageUtils.swift +227 -0
  201. package/ios/ImageView.swift +469 -0
  202. package/ios/Loaders/PhotoLibraryAssetLoader.swift +146 -0
  203. package/ios/Thumbhash.swift +550 -0
  204. package/ios/ThumbhashLoader.swift +53 -0
  205. package/jest.config.js +3 -0
  206. package/kepler/ExpoImage.cpp +63 -0
  207. package/kepler/ExpoImage.h +29 -0
  208. package/kepler/ExpoImageCacheHandler.cpp +15 -0
  209. package/kepler/ExpoImageCacheHandler.h +28 -0
  210. package/kepler/ExpoImageComponent.cpp +579 -0
  211. package/kepler/ExpoImageComponent.h +199 -0
  212. package/kepler/ImageWrapper/BlurhashImageWrapper.cpp +37 -0
  213. package/kepler/ImageWrapper/BlurhashImageWrapper.h +23 -0
  214. package/kepler/ImageWrapper/CacheImageWrapper.cpp +80 -0
  215. package/kepler/ImageWrapper/CacheImageWrapper.h +41 -0
  216. package/kepler/ImageWrapper/HashImageWrapper.cpp +21 -0
  217. package/kepler/ImageWrapper/HashImageWrapper.h +22 -0
  218. package/kepler/ImageWrapper/ImageWrapper.cpp +24 -0
  219. package/kepler/ImageWrapper/ImageWrapper.h +33 -0
  220. package/kepler/ImageWrapper/LocalImageWrapper.cpp +61 -0
  221. package/kepler/ImageWrapper/LocalImageWrapper.h +30 -0
  222. package/kepler/ImageWrapper/ThumbhashImageWrapper.cpp +33 -0
  223. package/kepler/ImageWrapper/ThumbhashImageWrapper.h +20 -0
  224. package/kepler/KeplerImageViewComponent.h +64 -0
  225. package/kepler/KeplerInit.cpp +35 -0
  226. package/kepler/blurhash/common.h +32 -0
  227. package/kepler/blurhash/decode.cpp +187 -0
  228. package/kepler/blurhash/decode.h +58 -0
  229. package/kepler/contentposition/ImageContentPosition.cpp +86 -0
  230. package/kepler/contentposition/ImageContentPosition.h +48 -0
  231. package/kepler/enums/ImageContentFit.h +17 -0
  232. package/kepler/enums/ImagePriority.h +17 -0
  233. package/kepler/managers/ExpoImageManager.cpp +38 -0
  234. package/kepler/managers/ExpoImageManager.h +23 -0
  235. package/kepler/test/ExpoImage.cpp +73 -0
  236. package/kepler/test/ExpoImageComponent.cpp +290 -0
  237. package/kepler/test/blurhash/decode.cpp +39 -0
  238. package/kepler/test/contentposition/ImageContentPosition.cpp +100 -0
  239. package/kepler/test/managers/ExpoImageManager.cpp +39 -0
  240. package/kepler/test/thumbhash/decode.cpp +18 -0
  241. package/kepler/test/utils/LoaderDelegate.cpp +130 -0
  242. package/kepler/thumbhash/decode.cpp +166 -0
  243. package/kepler/thumbhash/decode.h +22 -0
  244. package/kepler/utils/PlaceholderImageLoaderDelegate.cpp +26 -0
  245. package/kepler/utils/PlaceholderImageLoaderDelegate.h +26 -0
  246. package/kepler/utils/PrefetchImageLoaderDelegate.cpp +55 -0
  247. package/kepler/utils/PrefetchImageLoaderDelegate.h +35 -0
  248. package/kepler/utils/SourceImageLoaderDelegate.cpp +47 -0
  249. package/kepler/utils/SourceImageLoaderDelegate.h +42 -0
  250. package/kepler/utils/SourceImageLoaderDelegateEmitter.cpp +15 -0
  251. package/kepler/utils/SourceImageLoaderDelegateEmitter.h +28 -0
  252. package/kepler-compatibility.json +8 -0
  253. package/package.json +1 -0
  254. package/react-native.config.js +22 -0
  255. package/src/ExpoImage.kepler.tsx +119 -0
  256. package/src/ExpoImage.tsx +119 -0
  257. package/src/ExpoImage.web.tsx +155 -0
  258. package/src/ExpoImageModules.kepler.ts +7 -0
  259. package/src/ExpoImageModules.ts +4 -0
  260. package/src/Image.tsx +82 -0
  261. package/src/Image.types.ts +450 -0
  262. package/src/ImageBackground.tsx +18 -0
  263. package/src/ImageViewNativeComponent.ts +59 -0
  264. package/src/NativeImageLoaderKepler.ts +22 -0
  265. package/src/fabric/registerNativeComponent.ts +70 -0
  266. package/src/index.ts +3 -0
  267. package/src/utils/AssetSourceResolver.web.ts +88 -0
  268. package/src/utils/blurhash/base83.ts +104 -0
  269. package/src/utils/blurhash/decode.ts +114 -0
  270. package/src/utils/blurhash/error.ts +7 -0
  271. package/src/utils/blurhash/useBlurhash.tsx +81 -0
  272. package/src/utils/blurhash/utils.ts +21 -0
  273. package/src/utils/resolveAssetSource.tsx +3 -0
  274. package/src/utils/resolveAssetSource.web.ts +40 -0
  275. package/src/utils/resolveHashString.tsx +36 -0
  276. package/src/utils/resolveHashString.web.tsx +33 -0
  277. package/src/utils/resolveSources.tsx +48 -0
  278. package/src/utils/thumbhash/thumbhash.ts +387 -0
  279. package/src/utils.ts +109 -0
  280. package/src/web/AnimationManager.tsx +224 -0
  281. package/src/web/ColorTintFilter.tsx +36 -0
  282. package/src/web/ImageWrapper.tsx +85 -0
  283. package/src/web/ImageWrapper.types.ts +36 -0
  284. package/src/web/getImageWrapperEventHandler.ts +29 -0
  285. package/src/web/hooks.ts +72 -0
  286. package/src/web/imageStyles.tsx +136 -0
  287. package/src/web/positioning.ts +48 -0
  288. package/src/web/useSourceSelection.ts +151 -0
  289. package/tsconfig.json +9 -0
@@ -0,0 +1,189 @@
1
+ package expo.modules.image.blurhash
2
+
3
+ import android.graphics.Bitmap
4
+ import android.graphics.Color
5
+ import kotlin.math.cos
6
+ import kotlin.math.pow
7
+ import kotlin.math.withSign
8
+
9
+ /**
10
+ * Copied from https://github.com/woltapp/blurhash.
11
+ */
12
+ object BlurhashDecoder {
13
+
14
+ // cache Math.cos() calculations to improve performance.
15
+ // The number of calculations can be huge for many bitmaps: width * height * numCompX * numCompY * 2 * nBitmaps
16
+ // the cache is enabled by default, it is recommended to disable it only when just a few images are displayed
17
+ private val cacheCosinesX = HashMap<Int, DoubleArray>()
18
+ private val cacheCosinesY = HashMap<Int, DoubleArray>()
19
+
20
+ /**
21
+ * Clear calculations stored in memory cache.
22
+ * The cache is not big, but will increase when many image sizes are used,
23
+ * if the app needs memory it is recommended to clear it.
24
+ */
25
+ fun clearCache() {
26
+ cacheCosinesX.clear()
27
+ cacheCosinesY.clear()
28
+ }
29
+
30
+ /**
31
+ * Decode a blur hash into a new bitmap.
32
+ *
33
+ * @param useCache use in memory cache for the calculated math, reused by images with same size.
34
+ * if the cache does not exist yet it will be created and populated with new calculations.
35
+ * By default it is true.
36
+ */
37
+ fun decode(blurHash: String?, width: Int, height: Int, punch: Float = 1f, useCache: Boolean = true): Bitmap? {
38
+ if (blurHash == null || blurHash.length < 6) {
39
+ return null
40
+ }
41
+ val numCompEnc = decode83(blurHash, 0, 1)
42
+ val numCompX = (numCompEnc % 9) + 1
43
+ val numCompY = (numCompEnc / 9) + 1
44
+ if (blurHash.length != 4 + 2 * numCompX * numCompY) {
45
+ return null
46
+ }
47
+ val maxAcEnc = decode83(blurHash, 1, 2)
48
+ val maxAc = (maxAcEnc + 1) / 166f
49
+ val colors = Array(numCompX * numCompY) { i ->
50
+ if (i == 0) {
51
+ val colorEnc = decode83(blurHash, 2, 6)
52
+ decodeDc(colorEnc)
53
+ } else {
54
+ val from = 4 + i * 2
55
+ val colorEnc = decode83(blurHash, from, from + 2)
56
+ decodeAc(colorEnc, maxAc * punch)
57
+ }
58
+ }
59
+ return composeBitmap(width, height, numCompX, numCompY, colors, useCache)
60
+ }
61
+
62
+ private fun decode83(str: String, from: Int = 0, to: Int = str.length): Int {
63
+ var result = 0
64
+ for (i in from until to) {
65
+ val index = charMap[str[i]] ?: -1
66
+ if (index != -1) {
67
+ result = result * 83 + index
68
+ }
69
+ }
70
+ return result
71
+ }
72
+
73
+ private fun decodeDc(colorEnc: Int): FloatArray {
74
+ val r = colorEnc shr 16
75
+ val g = (colorEnc shr 8) and 255
76
+ val b = colorEnc and 255
77
+ return floatArrayOf(srgbToLinear(r), srgbToLinear(g), srgbToLinear(b))
78
+ }
79
+
80
+ private fun srgbToLinear(colorEnc: Int): Float {
81
+ val v = colorEnc / 255f
82
+ return if (v <= 0.04045f) {
83
+ (v / 12.92f)
84
+ } else {
85
+ ((v + 0.055f) / 1.055f).pow(2.4f)
86
+ }
87
+ }
88
+
89
+ private fun decodeAc(value: Int, maxAc: Float): FloatArray {
90
+ val r = value / (19 * 19)
91
+ val g = (value / 19) % 19
92
+ val b = value % 19
93
+ return floatArrayOf(
94
+ signedPow2((r - 9) / 9.0f) * maxAc,
95
+ signedPow2((g - 9) / 9.0f) * maxAc,
96
+ signedPow2((b - 9) / 9.0f) * maxAc
97
+ )
98
+ }
99
+
100
+ private fun signedPow2(value: Float) = value.pow(2f).withSign(value)
101
+
102
+ private fun composeBitmap(
103
+ width: Int,
104
+ height: Int,
105
+ numCompX: Int,
106
+ numCompY: Int,
107
+ colors: Array<FloatArray>,
108
+ useCache: Boolean
109
+ ): Bitmap {
110
+ // use an array for better performance when writing pixel colors
111
+ val imageArray = IntArray(width * height)
112
+ val calculateCosX = !useCache || !cacheCosinesX.containsKey(width * numCompX)
113
+ val cosinesX = getArrayForCosinesX(calculateCosX, width, numCompX)
114
+ val calculateCosY = !useCache || !cacheCosinesY.containsKey(height * numCompY)
115
+ val cosinesY = getArrayForCosinesY(calculateCosY, height, numCompY)
116
+ for (y in 0 until height) {
117
+ for (x in 0 until width) {
118
+ var r = 0f
119
+ var g = 0f
120
+ var b = 0f
121
+ for (j in 0 until numCompY) {
122
+ for (i in 0 until numCompX) {
123
+ val cosX = cosinesX.getCos(calculateCosX, i, numCompX, x, width)
124
+ val cosY = cosinesY.getCos(calculateCosY, j, numCompY, y, height)
125
+ val basis = (cosX * cosY).toFloat()
126
+ val color = colors[j * numCompX + i]
127
+ r += color[0] * basis
128
+ g += color[1] * basis
129
+ b += color[2] * basis
130
+ }
131
+ }
132
+ imageArray[x + width * y] = Color.rgb(linearToSrgb(r), linearToSrgb(g), linearToSrgb(b))
133
+ }
134
+ }
135
+ return Bitmap.createBitmap(imageArray, width, height, Bitmap.Config.ARGB_8888)
136
+ }
137
+
138
+ private fun getArrayForCosinesY(calculate: Boolean, height: Int, numCompY: Int) = when {
139
+ calculate -> {
140
+ DoubleArray(height * numCompY).also {
141
+ cacheCosinesY[height * numCompY] = it
142
+ }
143
+ }
144
+ else -> {
145
+ cacheCosinesY[height * numCompY]!!
146
+ }
147
+ }
148
+
149
+ private fun getArrayForCosinesX(calculate: Boolean, width: Int, numCompX: Int) = when {
150
+ calculate -> {
151
+ DoubleArray(width * numCompX).also {
152
+ cacheCosinesX[width * numCompX] = it
153
+ }
154
+ }
155
+ else -> cacheCosinesX[width * numCompX]!!
156
+ }
157
+
158
+ private fun DoubleArray.getCos(
159
+ calculate: Boolean,
160
+ x: Int,
161
+ numComp: Int,
162
+ y: Int,
163
+ size: Int
164
+ ): Double {
165
+ if (calculate) {
166
+ this[x + numComp * y] = cos(Math.PI * y * x / size)
167
+ }
168
+ return this[x + numComp * y]
169
+ }
170
+
171
+ private fun linearToSrgb(value: Float): Int {
172
+ val v = value.coerceIn(0f, 1f)
173
+ return if (v <= 0.0031308f) {
174
+ (v * 12.92f * 255f + 0.5f).toInt()
175
+ } else {
176
+ ((1.055f * v.pow(1 / 2.4f) - 0.055f) * 255 + 0.5f).toInt()
177
+ }
178
+ }
179
+
180
+ private val charMap = listOf(
181
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
182
+ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
183
+ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
184
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '#', '$', '%', '*', '+', ',',
185
+ '-', '.', ':', ';', '=', '?', '@', '[', ']', '^', '_', '{', '|', '}', '~'
186
+ )
187
+ .mapIndexed { i, c -> c to i }
188
+ .toMap()
189
+ }
@@ -0,0 +1,30 @@
1
+ package expo.modules.image.blurhash
2
+
3
+ import android.graphics.Bitmap
4
+ import android.net.Uri
5
+ import com.bumptech.glide.load.Options
6
+ import com.bumptech.glide.load.model.ModelLoader
7
+ import com.bumptech.glide.signature.ObjectKey
8
+ import expo.modules.image.GlideBlurhashModel
9
+
10
+ class BlurhashModelLoader : ModelLoader<GlideBlurhashModel, Bitmap> {
11
+ override fun handles(model: GlideBlurhashModel): Boolean = true
12
+
13
+ override fun buildLoadData(
14
+ model: GlideBlurhashModel,
15
+ width: Int,
16
+ height: Int,
17
+ options: Options
18
+ ): ModelLoader.LoadData<Bitmap> {
19
+ val blurhash = getPath(model.uri, 0, null) { it }
20
+ return ModelLoader.LoadData(
21
+ ObjectKey(model),
22
+ BlurHashFetcher(blurhash, model.width, model.height, 1f)
23
+ )
24
+ }
25
+
26
+ private fun <T> getPath(uri: Uri, index: Int, default: T, converter: (String) -> T): T {
27
+ val value = uri.pathSegments.getOrNull(index) ?: return default
28
+ return converter(value)
29
+ }
30
+ }
@@ -0,0 +1,14 @@
1
+ package expo.modules.image.blurhash
2
+
3
+ import android.graphics.Bitmap
4
+ import com.bumptech.glide.load.model.ModelLoader
5
+ import com.bumptech.glide.load.model.ModelLoaderFactory
6
+ import com.bumptech.glide.load.model.MultiModelLoaderFactory
7
+ import expo.modules.image.GlideBlurhashModel
8
+
9
+ class BlurhashModelLoaderFactory : ModelLoaderFactory<GlideBlurhashModel, Bitmap> {
10
+ override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<GlideBlurhashModel, Bitmap> =
11
+ BlurhashModelLoader()
12
+
13
+ override fun teardown() = Unit
14
+ }
@@ -0,0 +1,17 @@
1
+ package expo.modules.image.blurhash
2
+
3
+ import android.content.Context
4
+ import android.graphics.Bitmap
5
+ import com.bumptech.glide.Glide
6
+ import com.bumptech.glide.Registry
7
+ import com.bumptech.glide.annotation.GlideModule
8
+ import com.bumptech.glide.module.LibraryGlideModule
9
+ import expo.modules.image.GlideBlurhashModel
10
+
11
+ @GlideModule
12
+ class BlurhashModule : LibraryGlideModule() {
13
+ override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
14
+ super.registerComponents(context, glide, registry)
15
+ registry.prepend(GlideBlurhashModel::class.java, Bitmap::class.java, BlurhashModelLoaderFactory())
16
+ }
17
+ }
@@ -0,0 +1,27 @@
1
+ package expo.modules.image.dataurls
2
+
3
+ import android.util.Base64
4
+ import com.bumptech.glide.Priority
5
+ import com.bumptech.glide.load.DataSource
6
+ import com.bumptech.glide.load.data.DataFetcher
7
+ import java.nio.ByteBuffer
8
+
9
+ class Base64DataFetcher(private val data: String) : DataFetcher<ByteBuffer> {
10
+ override fun cleanup() = Unit
11
+ override fun cancel() = Unit
12
+ override fun getDataClass(): Class<ByteBuffer> = ByteBuffer::class.java
13
+ override fun getDataSource(): DataSource = DataSource.LOCAL
14
+
15
+ override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in ByteBuffer>) {
16
+ val base64Section = getBase64Section()
17
+ val data = Base64.decode(base64Section, Base64.DEFAULT)
18
+ val byteBuffer = ByteBuffer.wrap(data)
19
+ callback.onDataReady(byteBuffer)
20
+ }
21
+
22
+ private fun getBase64Section(): String {
23
+ // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs.
24
+ val startOfBase64Section = data.indexOf(',')
25
+ return data.substring(startOfBase64Section + 1)
26
+ }
27
+ }
@@ -0,0 +1,24 @@
1
+ package expo.modules.image.dataurls
2
+
3
+ import com.bumptech.glide.load.Options
4
+ import com.bumptech.glide.load.model.ModelLoader
5
+ import com.bumptech.glide.signature.ObjectKey
6
+ import java.nio.ByteBuffer
7
+
8
+ /**
9
+ * Loads an [java.io.InputStream] from a Base 64 encoded String.
10
+ */
11
+ class Base64ModelLoader : ModelLoader<String, ByteBuffer> {
12
+ override fun handles(model: String): Boolean {
13
+ return model.startsWith("data:")
14
+ }
15
+
16
+ override fun buildLoadData(
17
+ model: String,
18
+ width: Int,
19
+ height: Int,
20
+ options: Options
21
+ ): ModelLoader.LoadData<ByteBuffer> {
22
+ return ModelLoader.LoadData(ObjectKey(model), Base64DataFetcher(model))
23
+ }
24
+ }
@@ -0,0 +1,12 @@
1
+ package expo.modules.image.dataurls
2
+
3
+ import com.bumptech.glide.load.model.ModelLoader
4
+ import com.bumptech.glide.load.model.ModelLoaderFactory
5
+ import com.bumptech.glide.load.model.MultiModelLoaderFactory
6
+ import java.nio.ByteBuffer
7
+
8
+ class Base64ModelLoaderFactory : ModelLoaderFactory<String, ByteBuffer> {
9
+ override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<String, ByteBuffer> =
10
+ Base64ModelLoader()
11
+ override fun teardown() = Unit
12
+ }
@@ -0,0 +1,16 @@
1
+ package expo.modules.image.dataurls
2
+
3
+ import android.content.Context
4
+ import com.bumptech.glide.Glide
5
+ import com.bumptech.glide.Registry
6
+ import com.bumptech.glide.annotation.GlideModule
7
+ import com.bumptech.glide.module.LibraryGlideModule
8
+ import java.nio.ByteBuffer
9
+
10
+ @GlideModule
11
+ class Base64Module : LibraryGlideModule() {
12
+ override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
13
+ super.registerComponents(context, glide, registry)
14
+ registry.prepend(String::class.java, ByteBuffer::class.java, Base64ModelLoaderFactory())
15
+ }
16
+ }
@@ -0,0 +1,201 @@
1
+ package expo.modules.image.drawing
2
+
3
+ import android.content.Context
4
+ import android.graphics.Canvas
5
+ import android.graphics.Outline
6
+ import android.graphics.Path
7
+ import android.graphics.RectF
8
+ import android.view.View
9
+ import android.view.ViewOutlineProvider
10
+ import com.facebook.react.modules.i18nmanager.I18nUtil
11
+ import com.facebook.react.uimanager.FloatUtil
12
+ import com.facebook.react.uimanager.PixelUtil
13
+ import com.facebook.yoga.YogaConstants
14
+ import expo.modules.image.ifYogaUndefinedUse
15
+
16
+ class OutlineProvider(private val mContext: Context) : ViewOutlineProvider() {
17
+ enum class BorderRadiusConfig {
18
+ ALL, TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT, TOP_START, TOP_END, BOTTOM_START, BOTTOM_END
19
+ }
20
+
21
+ enum class CornerRadius {
22
+ TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT
23
+ }
24
+
25
+ private var mLayoutDirection = View.LAYOUT_DIRECTION_LTR
26
+ private val mBounds = RectF()
27
+ val borderRadiiConfig = FloatArray(9) { YogaConstants.UNDEFINED }
28
+ private val mCornerRadii = FloatArray(4)
29
+ private var mCornerRadiiInvalidated = true
30
+ private val mConvexPath = Path()
31
+ private var mConvexPathInvalidated = true
32
+
33
+ init {
34
+ updateCornerRadiiIfNeeded()
35
+ }
36
+
37
+ private fun updateCornerRadiiIfNeeded() {
38
+ if (!mCornerRadiiInvalidated) {
39
+ return
40
+ }
41
+
42
+ val isRTL = mLayoutDirection == View.LAYOUT_DIRECTION_RTL
43
+ val isRTLSwap = I18nUtil.getInstance().doLeftAndRightSwapInRTL(mContext)
44
+ updateCornerRadius(
45
+ CornerRadius.TOP_LEFT,
46
+ BorderRadiusConfig.TOP_LEFT,
47
+ BorderRadiusConfig.TOP_RIGHT,
48
+ BorderRadiusConfig.TOP_START,
49
+ BorderRadiusConfig.TOP_END,
50
+ isRTL,
51
+ isRTLSwap
52
+ )
53
+ updateCornerRadius(
54
+ CornerRadius.TOP_RIGHT,
55
+ BorderRadiusConfig.TOP_RIGHT,
56
+ BorderRadiusConfig.TOP_LEFT,
57
+ BorderRadiusConfig.TOP_END,
58
+ BorderRadiusConfig.TOP_START,
59
+ isRTL,
60
+ isRTLSwap
61
+ )
62
+ updateCornerRadius(
63
+ CornerRadius.BOTTOM_LEFT,
64
+ BorderRadiusConfig.BOTTOM_LEFT,
65
+ BorderRadiusConfig.BOTTOM_RIGHT,
66
+ BorderRadiusConfig.BOTTOM_START,
67
+ BorderRadiusConfig.BOTTOM_END,
68
+ isRTL,
69
+ isRTLSwap
70
+ )
71
+ updateCornerRadius(
72
+ CornerRadius.BOTTOM_RIGHT,
73
+ BorderRadiusConfig.BOTTOM_RIGHT,
74
+ BorderRadiusConfig.BOTTOM_LEFT,
75
+ BorderRadiusConfig.BOTTOM_END,
76
+ BorderRadiusConfig.BOTTOM_START,
77
+ isRTL,
78
+ isRTLSwap
79
+ )
80
+ mCornerRadiiInvalidated = false
81
+ mConvexPathInvalidated = true
82
+ }
83
+
84
+ private fun updateCornerRadius(
85
+ outputPosition: CornerRadius,
86
+ inputPosition: BorderRadiusConfig,
87
+ oppositePosition: BorderRadiusConfig,
88
+ startPosition: BorderRadiusConfig,
89
+ endPosition: BorderRadiusConfig,
90
+ isRTL: Boolean,
91
+ isRTLSwap: Boolean
92
+ ) {
93
+ var radius = borderRadiiConfig[inputPosition.ordinal]
94
+ if (isRTL) {
95
+ if (isRTLSwap) {
96
+ radius = borderRadiiConfig[oppositePosition.ordinal]
97
+ }
98
+ if (YogaConstants.isUndefined(radius)) {
99
+ radius = borderRadiiConfig[endPosition.ordinal]
100
+ }
101
+ } else {
102
+ if (YogaConstants.isUndefined(radius)) {
103
+ radius = borderRadiiConfig[startPosition.ordinal]
104
+ }
105
+ }
106
+ radius = radius
107
+ .ifYogaUndefinedUse(borderRadiiConfig[BorderRadiusConfig.ALL.ordinal])
108
+ .ifYogaUndefinedUse(0f)
109
+ mCornerRadii[outputPosition.ordinal] = PixelUtil.toPixelFromDIP(radius)
110
+ }
111
+
112
+ private fun updateConvexPathIfNeeded() {
113
+ if (!mConvexPathInvalidated) {
114
+ return
115
+ }
116
+ mConvexPath.reset()
117
+ mConvexPath.addRoundRect(
118
+ mBounds,
119
+ floatArrayOf(
120
+ mCornerRadii[CornerRadius.TOP_LEFT.ordinal],
121
+ mCornerRadii[CornerRadius.TOP_LEFT.ordinal],
122
+ mCornerRadii[CornerRadius.TOP_RIGHT.ordinal],
123
+ mCornerRadii[CornerRadius.TOP_RIGHT.ordinal],
124
+ mCornerRadii[CornerRadius.BOTTOM_RIGHT.ordinal],
125
+ mCornerRadii[CornerRadius.BOTTOM_RIGHT.ordinal],
126
+ mCornerRadii[CornerRadius.BOTTOM_LEFT.ordinal],
127
+ mCornerRadii[CornerRadius.BOTTOM_LEFT.ordinal]
128
+ ),
129
+ Path.Direction.CW
130
+ )
131
+ mConvexPathInvalidated = false
132
+ }
133
+
134
+ fun hasEqualCorners(): Boolean {
135
+ updateCornerRadiiIfNeeded()
136
+ val initialCornerRadius = mCornerRadii[0]
137
+ return mCornerRadii.all { initialCornerRadius == it }
138
+ }
139
+
140
+ fun setBorderRadius(radius: Float, position: Int): Boolean {
141
+ if (!FloatUtil.floatsEqual(borderRadiiConfig[position], radius)) {
142
+ borderRadiiConfig[position] = radius
143
+ mCornerRadiiInvalidated = true
144
+ return true
145
+ }
146
+ return false
147
+ }
148
+
149
+ private fun updateBoundsAndLayoutDirection(view: View) {
150
+ // Update layout direction
151
+ val layoutDirection = view.layoutDirection
152
+ if (mLayoutDirection != layoutDirection) {
153
+ mLayoutDirection = layoutDirection
154
+ mCornerRadiiInvalidated = true
155
+ }
156
+
157
+ // Update size
158
+ val left = 0
159
+ val top = 0
160
+ val right = view.width
161
+ val bottom = view.height
162
+ if (mBounds.left != left.toFloat() ||
163
+ mBounds.top != top.toFloat() ||
164
+ mBounds.right != right.toFloat() ||
165
+ mBounds.bottom != bottom.toFloat()
166
+ ) {
167
+ mBounds[left.toFloat(), top.toFloat(), right.toFloat()] = bottom.toFloat()
168
+ mCornerRadiiInvalidated = true
169
+ }
170
+ }
171
+
172
+ override fun getOutline(view: View, outline: Outline) {
173
+ updateBoundsAndLayoutDirection(view)
174
+
175
+ // Calculate outline
176
+ updateCornerRadiiIfNeeded()
177
+ if (hasEqualCorners()) {
178
+ val cornerRadius = mCornerRadii[0]
179
+ if (cornerRadius > 0) {
180
+ outline.setRoundRect(0, 0, mBounds.width().toInt(), mBounds.height().toInt(), cornerRadius)
181
+ } else {
182
+ outline.setRect(0, 0, mBounds.width().toInt(), mBounds.height().toInt())
183
+ }
184
+ } else {
185
+ // Clipping is not supported when using a convex path, but drawing the elevation
186
+ // shadow is. For the particular case, we fallback to canvas clipping in the view
187
+ // which is supposed to call `clipCanvasIfNeeded` in its `draw` method.
188
+ updateConvexPathIfNeeded()
189
+ outline.setConvexPath(mConvexPath)
190
+ }
191
+ }
192
+
193
+ fun clipCanvasIfNeeded(canvas: Canvas, view: View) {
194
+ updateBoundsAndLayoutDirection(view)
195
+ updateCornerRadiiIfNeeded()
196
+ if (!hasEqualCorners()) {
197
+ updateConvexPathIfNeeded()
198
+ canvas.clipPath(mConvexPath)
199
+ }
200
+ }
201
+ }
@@ -0,0 +1,89 @@
1
+ package expo.modules.image.enums
2
+
3
+ import android.graphics.Matrix
4
+ import android.graphics.RectF
5
+ import expo.modules.kotlin.types.Enumerable
6
+ import kotlin.math.max
7
+
8
+ /**
9
+ * Describes how the image should be resized to fit its container.
10
+ * - Note: It mirrors the CSS [`object-fit`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) property.
11
+ */
12
+ enum class ContentFit(val value: String) : Enumerable {
13
+ /**
14
+ * The image is scaled to maintain its aspect ratio while fitting within the container's box.
15
+ * The entire image is made to fill the box, while preserving its aspect ratio,
16
+ * so the image will be "letterboxed" if its aspect ratio does not match the aspect ratio of the box.
17
+ */
18
+ Contain("contain"),
19
+
20
+ /**
21
+ * The image is sized to maintain its aspect ratio while filling the element's entire content box.
22
+ * If the image's aspect ratio does not match the aspect ratio of its box, then the object will be clipped to fit.
23
+ */
24
+ Cover("cover"),
25
+
26
+ /**
27
+ * The image is sized to fill the element's content box. The entire object will completely fill the box.
28
+ * If the image's aspect ratio does not match the aspect ratio of its box, then the image will be stretched to fit.
29
+ */
30
+ Fill("fill"),
31
+
32
+ /**
33
+ * The image is not resized and is centered by default.
34
+ * When specified, the exact position can be controlled with `ContentPosition`.
35
+ */
36
+ None("none"),
37
+
38
+ /**
39
+ * The image is sized as if `none` or `contain` were specified,
40
+ * whichever would result in a smaller concrete image size.
41
+ */
42
+ ScaleDown("scale-down");
43
+
44
+ internal fun toMatrix(imageRect: RectF, viewRect: RectF, sourceWidth: Int, sourceHeight: Int) = Matrix().apply {
45
+ when (this@ContentFit) {
46
+ Contain -> setRectToRect(imageRect, viewRect, Matrix.ScaleToFit.START)
47
+ Cover -> {
48
+ val imageWidth = imageRect.width()
49
+ val imageHeight = imageRect.height()
50
+
51
+ val reactWidth = viewRect.width()
52
+ val reactHeight = viewRect.height()
53
+
54
+ val scale = max(reactWidth / imageWidth, reactHeight / imageHeight)
55
+
56
+ setScale(scale, scale)
57
+ }
58
+ Fill -> setRectToRect(imageRect, viewRect, Matrix.ScaleToFit.FILL)
59
+ None -> {
60
+ // we don't need to do anything
61
+ }
62
+ ScaleDown -> {
63
+ // If we have information about the original size of the source, we can resize the image more drastically.
64
+ // In certain situations, we may even permit upscaling when we anticipate the image to be reloaded without any reduction in size,
65
+ // which will create a seamless transition between various states.
66
+ if (sourceWidth != -1 && sourceHeight != -1) {
67
+ val sourceRect = RectF(0f, 0f, sourceWidth.toFloat(), sourceHeight.toFloat())
68
+ // Rather than checking if the image rectangle is within the bounds of the view rectangle, we verify the original source rectangle.
69
+ // We know that the newly loaded image has larger dimensions than the current one, and therefore,
70
+ // it will not be downscaled.
71
+ if (sourceRect.width() >= viewRect.width() || sourceRect.height() >= viewRect.height()) {
72
+ setRectToRect(imageRect, viewRect, Matrix.ScaleToFit.START)
73
+ }
74
+ // If the source rectangle is larger than the view rectangle and the image rectangle has not been upscaled to match the source rectangle,
75
+ // temporary upscaling is necessary to ensure a seamless transition.
76
+ // It should be noted that this upscaling is applied to the downscaled version of the image,
77
+ // not the original source image, and will be replaced by the original asset shortly thereafter.
78
+ else {
79
+ setRectToRect(imageRect, sourceRect, Matrix.ScaleToFit.START)
80
+ }
81
+ } else {
82
+ if (imageRect.width() >= viewRect.width() || imageRect.height() >= viewRect.height()) {
83
+ setRectToRect(imageRect, viewRect, Matrix.ScaleToFit.START)
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,14 @@
1
+ package expo.modules.image.enums
2
+
3
+ import com.bumptech.glide.load.DataSource
4
+
5
+ enum class ImageCacheType(private vararg val dataSources: DataSource) {
6
+ NONE(DataSource.LOCAL, DataSource.REMOTE),
7
+ DISK(DataSource.DATA_DISK_CACHE, DataSource.RESOURCE_DISK_CACHE),
8
+ MEMORY(DataSource.MEMORY_CACHE);
9
+
10
+ companion object {
11
+ fun fromNativeValue(value: DataSource): ImageCacheType =
12
+ values().firstOrNull { it.dataSources.contains(value) } ?: NONE
13
+ }
14
+ }
@@ -0,0 +1,15 @@
1
+ package expo.modules.image.enums
2
+
3
+ import expo.modules.kotlin.types.Enumerable
4
+
5
+ enum class Priority(val value: String) : Enumerable {
6
+ LOW("low"),
7
+ NORMAL("normal"),
8
+ HIGH("high");
9
+
10
+ internal fun toGlidePriority(): com.bumptech.glide.Priority = when (this) {
11
+ LOW -> com.bumptech.glide.Priority.LOW
12
+ NORMAL -> com.bumptech.glide.Priority.NORMAL
13
+ HIGH -> com.bumptech.glide.Priority.IMMEDIATE
14
+ }
15
+ }