@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,88 @@
1
+ import { Platform } from 'expo-modules-core';
2
+ import { PixelRatio } from 'react-native';
3
+ import { PackagerAsset } from 'react-native/Libraries/Image/AssetRegistry';
4
+
5
+ export type ResolvedAssetSource = {
6
+ __packager_asset: boolean;
7
+ width?: number;
8
+ height?: number;
9
+ uri: string;
10
+ scale: number;
11
+ };
12
+
13
+ // Returns the Metro dev server-specific asset location.
14
+ function getScaledAssetPath(asset): string {
15
+ const scale = AssetSourceResolver.pickScale(asset.scales, PixelRatio.get());
16
+ const scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
17
+ const type = !asset.type ? '' : `.${asset.type}`;
18
+ if (__DEV__) {
19
+ return asset.httpServerLocation + '/' + asset.name + scaleSuffix + type;
20
+ } else {
21
+ return asset.httpServerLocation.replace(/\.\.\//g, '_') + '/' + asset.name + scaleSuffix + type;
22
+ }
23
+ }
24
+
25
+ export default class AssetSourceResolver {
26
+ serverUrl: string;
27
+ // where the jsbundle is being run from
28
+ // NOTE(EvanBacon): Never defined on web.
29
+ jsbundleUrl?: string | null;
30
+ // the asset to resolve
31
+ asset: PackagerAsset;
32
+
33
+ constructor(
34
+ serverUrl: string | undefined | null,
35
+ jsbundleUrl: string | undefined | null,
36
+ asset: PackagerAsset
37
+ ) {
38
+ this.serverUrl = serverUrl || 'https://expo.dev';
39
+ this.jsbundleUrl = null;
40
+ this.asset = asset;
41
+ }
42
+
43
+ // Always true for web runtimes
44
+ isLoadedFromServer(): boolean {
45
+ return true;
46
+ }
47
+
48
+ // Always false for web runtimes
49
+ isLoadedFromFileSystem(): boolean {
50
+ return false;
51
+ }
52
+
53
+ defaultAsset(): ResolvedAssetSource {
54
+ return this.assetServerURL();
55
+ }
56
+
57
+ /**
58
+ * @returns absolute remote URL for the hosted asset.
59
+ */
60
+ assetServerURL(): ResolvedAssetSource {
61
+ const fromUrl = new URL(getScaledAssetPath(this.asset), this.serverUrl);
62
+ fromUrl.searchParams.set('platform', Platform.OS);
63
+ fromUrl.searchParams.set('hash', this.asset.hash);
64
+ return this.fromSource(
65
+ // Relative on web
66
+ fromUrl.toString().replace(fromUrl.origin, '')
67
+ );
68
+ }
69
+
70
+ fromSource(source: string): ResolvedAssetSource {
71
+ return {
72
+ __packager_asset: true,
73
+ width: this.asset.width ?? undefined,
74
+ height: this.asset.height ?? undefined,
75
+ uri: source,
76
+ scale: AssetSourceResolver.pickScale(this.asset.scales, PixelRatio.get()),
77
+ };
78
+ }
79
+
80
+ static pickScale(scales: number[], deviceScale: number): number {
81
+ for (let i = 0; i < scales.length; i++) {
82
+ if (scales[i] >= deviceScale) {
83
+ return scales[i];
84
+ }
85
+ }
86
+ return scales[scales.length - 1] || 1;
87
+ }
88
+ }
@@ -0,0 +1,104 @@
1
+ const digitCharacters = [
2
+ '0',
3
+ '1',
4
+ '2',
5
+ '3',
6
+ '4',
7
+ '5',
8
+ '6',
9
+ '7',
10
+ '8',
11
+ '9',
12
+ 'A',
13
+ 'B',
14
+ 'C',
15
+ 'D',
16
+ 'E',
17
+ 'F',
18
+ 'G',
19
+ 'H',
20
+ 'I',
21
+ 'J',
22
+ 'K',
23
+ 'L',
24
+ 'M',
25
+ 'N',
26
+ 'O',
27
+ 'P',
28
+ 'Q',
29
+ 'R',
30
+ 'S',
31
+ 'T',
32
+ 'U',
33
+ 'V',
34
+ 'W',
35
+ 'X',
36
+ 'Y',
37
+ 'Z',
38
+ 'a',
39
+ 'b',
40
+ 'c',
41
+ 'd',
42
+ 'e',
43
+ 'f',
44
+ 'g',
45
+ 'h',
46
+ 'i',
47
+ 'j',
48
+ 'k',
49
+ 'l',
50
+ 'm',
51
+ 'n',
52
+ 'o',
53
+ 'p',
54
+ 'q',
55
+ 'r',
56
+ 's',
57
+ 't',
58
+ 'u',
59
+ 'v',
60
+ 'w',
61
+ 'x',
62
+ 'y',
63
+ 'z',
64
+ '#',
65
+ '$',
66
+ '%',
67
+ '*',
68
+ '+',
69
+ ',',
70
+ '-',
71
+ '.',
72
+ ':',
73
+ ';',
74
+ '=',
75
+ '?',
76
+ '@',
77
+ '[',
78
+ ']',
79
+ '^',
80
+ '_',
81
+ '{',
82
+ '|',
83
+ '}',
84
+ '~',
85
+ ];
86
+
87
+ export const decode83 = (str: string) => {
88
+ let value = 0;
89
+ for (let i = 0; i < str.length; i++) {
90
+ const c = str[i];
91
+ const digit = digitCharacters.indexOf(c);
92
+ value = value * 83 + digit;
93
+ }
94
+ return value;
95
+ };
96
+
97
+ export const encode83 = (n: number, length: number): string => {
98
+ let result = '';
99
+ for (let i = 1; i <= length; i++) {
100
+ const digit = (Math.floor(n) / Math.pow(83, length - i)) % 83;
101
+ result += digitCharacters[Math.floor(digit)];
102
+ }
103
+ return result;
104
+ };
@@ -0,0 +1,114 @@
1
+ import { decode83 } from './base83';
2
+ import { ValidationError } from './error';
3
+ import { sRGBToLinear, signPow, linearTosRGB } from './utils';
4
+
5
+ /**
6
+ * Returns an error message if invalid or undefined if valid
7
+ * @param blurhash
8
+ */
9
+ const validateBlurhash = (blurhash: string) => {
10
+ if (!blurhash || blurhash.length < 6) {
11
+ throw new ValidationError('The blurhash string must be at least 6 characters');
12
+ }
13
+
14
+ const sizeFlag = decode83(blurhash[0]);
15
+ const numY = Math.floor(sizeFlag / 9) + 1;
16
+ const numX = (sizeFlag % 9) + 1;
17
+
18
+ if (blurhash.length !== 4 + 2 * numX * numY) {
19
+ throw new ValidationError(
20
+ `blurhash length mismatch: length is ${blurhash.length} but it should be ${
21
+ 4 + 2 * numX * numY
22
+ }`
23
+ );
24
+ }
25
+ };
26
+
27
+ export const isBlurhashValid = (blurhash: string): { result: boolean; errorReason?: string } => {
28
+ try {
29
+ validateBlurhash(blurhash);
30
+ } catch (error) {
31
+ return { result: false, errorReason: error.message };
32
+ }
33
+
34
+ return { result: true };
35
+ };
36
+
37
+ const decodeDC = (value: number) => {
38
+ const intR = value >> 16;
39
+ const intG = (value >> 8) & 255;
40
+ const intB = value & 255;
41
+ return [sRGBToLinear(intR), sRGBToLinear(intG), sRGBToLinear(intB)];
42
+ };
43
+
44
+ const decodeAC = (value: number, maximumValue: number) => {
45
+ const quantR = Math.floor(value / (19 * 19));
46
+ const quantG = Math.floor(value / 19) % 19;
47
+ const quantB = value % 19;
48
+
49
+ const rgb = [
50
+ signPow((quantR - 9) / 9, 2.0) * maximumValue,
51
+ signPow((quantG - 9) / 9, 2.0) * maximumValue,
52
+ signPow((quantB - 9) / 9, 2.0) * maximumValue,
53
+ ];
54
+
55
+ return rgb;
56
+ };
57
+
58
+ const decode = (blurhash: string, width: number, height: number, punch?: number) => {
59
+ validateBlurhash(blurhash);
60
+
61
+ punch = (punch || 1) | 1;
62
+
63
+ const sizeFlag = decode83(blurhash[0]);
64
+ const numY = Math.floor(sizeFlag / 9) + 1;
65
+ const numX = (sizeFlag % 9) + 1;
66
+
67
+ const quantisedMaximumValue = decode83(blurhash[1]);
68
+ const maximumValue = (quantisedMaximumValue + 1) / 166;
69
+
70
+ const colors = new Array(numX * numY);
71
+
72
+ for (let i = 0; i < colors.length; i++) {
73
+ if (i === 0) {
74
+ const value = decode83(blurhash.substring(2, 6));
75
+ colors[i] = decodeDC(value);
76
+ } else {
77
+ const value = decode83(blurhash.substring(4 + i * 2, 6 + i * 2));
78
+ colors[i] = decodeAC(value, maximumValue * punch);
79
+ }
80
+ }
81
+
82
+ const bytesPerRow = width * 4;
83
+ const pixels = new Uint8ClampedArray(bytesPerRow * height);
84
+
85
+ for (let y = 0; y < height; y++) {
86
+ for (let x = 0; x < width; x++) {
87
+ let r = 0;
88
+ let g = 0;
89
+ let b = 0;
90
+
91
+ for (let j = 0; j < numY; j++) {
92
+ for (let i = 0; i < numX; i++) {
93
+ const basis = Math.cos((Math.PI * x * i) / width) * Math.cos((Math.PI * y * j) / height);
94
+ const color = colors[i + j * numX];
95
+ r += color[0] * basis;
96
+ g += color[1] * basis;
97
+ b += color[2] * basis;
98
+ }
99
+ }
100
+
101
+ const intR = linearTosRGB(r);
102
+ const intG = linearTosRGB(g);
103
+ const intB = linearTosRGB(b);
104
+
105
+ pixels[4 * x + 0 + y * bytesPerRow] = intR;
106
+ pixels[4 * x + 1 + y * bytesPerRow] = intG;
107
+ pixels[4 * x + 2 + y * bytesPerRow] = intB;
108
+ pixels[4 * x + 3 + y * bytesPerRow] = 255; // alpha
109
+ }
110
+ }
111
+ return pixels;
112
+ };
113
+
114
+ export default decode;
@@ -0,0 +1,7 @@
1
+ export class ValidationError extends Error {
2
+ constructor(message: string) {
3
+ super(message);
4
+ this.name = 'ValidationError';
5
+ this.message = message;
6
+ }
7
+ }
@@ -0,0 +1,81 @@
1
+ // adapted from https://gist.github.com/ngbrown/d62eb518753378eb0a9bf02bb4723235
2
+ // modified from https://gist.github.com/WorldMaker/a3cbe0059acd827edee568198376b95a
3
+ // https://github.com/woltapp/react-blurhash/issues/3
4
+
5
+ import { useEffect, useState, useMemo } from 'react';
6
+
7
+ import decode from './decode';
8
+ import { isBlurhashString } from '../resolveSources';
9
+
10
+ const DEFAULT_SIZE = {
11
+ width: 32,
12
+ height: 32,
13
+ };
14
+
15
+ // We scale up the canvas to avoid an irritating visual glitch when animating in Chrome.
16
+ const scaleRatio = 10;
17
+
18
+ export function useBlurhash(
19
+ blurhash: { uri?: string; width?: number; height?: number } | undefined | null,
20
+ punch: number = 1
21
+ ) {
22
+ punch = punch || 1;
23
+
24
+ const [uri, setUri] = useState<string | null>(null);
25
+
26
+ useEffect(() => {
27
+ let isCanceled = false;
28
+
29
+ if (!blurhash || !blurhash.uri || !isBlurhashString(blurhash.uri)) return;
30
+
31
+ const pixels = decode(
32
+ blurhash.uri,
33
+ blurhash?.width ?? DEFAULT_SIZE.width,
34
+ blurhash?.height ?? DEFAULT_SIZE.height,
35
+ punch
36
+ );
37
+
38
+ const canvas = document.createElement('canvas');
39
+ const upscaledCanvas = document.createElement('canvas');
40
+ canvas.width = blurhash?.width ?? DEFAULT_SIZE.width;
41
+ canvas.height = blurhash?.height ?? DEFAULT_SIZE.height;
42
+ upscaledCanvas.width = (blurhash?.width ?? DEFAULT_SIZE.width) * scaleRatio;
43
+ upscaledCanvas.height = (blurhash?.height ?? DEFAULT_SIZE.height) * scaleRatio;
44
+ const context = canvas.getContext('2d');
45
+ if (!context) {
46
+ console.warn('Failed to decode blurhash');
47
+ return;
48
+ }
49
+ const imageData = context.createImageData(canvas.width, canvas.height);
50
+ imageData.data.set(pixels);
51
+ context.putImageData(imageData, 0, 0);
52
+ const upscaledContext = upscaledCanvas.getContext('2d');
53
+ if (!upscaledContext) {
54
+ console.warn('Failed to decode blurhash');
55
+ return;
56
+ }
57
+ upscaledContext.scale(scaleRatio, scaleRatio);
58
+ upscaledContext.drawImage(canvas, 0, 0);
59
+ upscaledCanvas.toBlob((blob) => {
60
+ if (!isCanceled) {
61
+ setUri((oldUrl) => {
62
+ if (oldUrl) {
63
+ URL.revokeObjectURL(oldUrl);
64
+ }
65
+ return blob ? URL.createObjectURL(blob) : oldUrl;
66
+ });
67
+ }
68
+ });
69
+
70
+ return function cleanupBlurhash() {
71
+ isCanceled = true;
72
+ setUri((oldUrl) => {
73
+ if (oldUrl) {
74
+ URL.revokeObjectURL(oldUrl);
75
+ }
76
+ return null;
77
+ });
78
+ };
79
+ }, [blurhash?.uri, blurhash?.height, blurhash?.width, punch]);
80
+ return useMemo(() => (uri ? { uri } : null), [uri]);
81
+ }
@@ -0,0 +1,21 @@
1
+ export const sRGBToLinear = (value: number) => {
2
+ const v = value / 255;
3
+ if (v <= 0.04045) {
4
+ return v / 12.92;
5
+ } else {
6
+ return Math.pow((v + 0.055) / 1.055, 2.4);
7
+ }
8
+ };
9
+
10
+ export const linearTosRGB = (value: number) => {
11
+ const v = Math.max(0, Math.min(1, value));
12
+ if (v <= 0.0031308) {
13
+ return Math.trunc(v * 12.92 * 255 + 0.5);
14
+ } else {
15
+ return Math.trunc((1.055 * Math.pow(v, 1 / 2.4) - 0.055) * 255 + 0.5);
16
+ }
17
+ };
18
+
19
+ export const sign = (n: number) => (n < 0 ? -1 : 1);
20
+
21
+ export const signPow = (val: number, exp: number) => sign(val) * Math.pow(Math.abs(val), exp);
@@ -0,0 +1,3 @@
1
+ import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
2
+
3
+ export default resolveAssetSource;
@@ -0,0 +1,40 @@
1
+ import { getAssetByID } from 'react-native/Libraries/Image/AssetRegistry';
2
+
3
+ import AssetSourceResolver, { ResolvedAssetSource } from './AssetSourceResolver.web';
4
+
5
+ let _customSourceTransformer;
6
+
7
+ export function setCustomSourceTransformer(
8
+ transformer: (resolver: AssetSourceResolver) => ResolvedAssetSource
9
+ ): void {
10
+ _customSourceTransformer = transformer;
11
+ }
12
+
13
+ /**
14
+ * `source` is either a number (opaque type returned by require('./foo.png'))
15
+ * or an `ImageSource` like { uri: '<http location || file path>' }
16
+ */
17
+ export default function resolveAssetSource(source: any): ResolvedAssetSource | undefined {
18
+ if (typeof source === 'object') {
19
+ return source;
20
+ }
21
+
22
+ const asset = getAssetByID(source);
23
+ if (!asset) {
24
+ return undefined;
25
+ }
26
+
27
+ const resolver = new AssetSourceResolver('https://expo.dev', null, asset);
28
+ if (_customSourceTransformer) {
29
+ return _customSourceTransformer(resolver);
30
+ }
31
+ return resolver.defaultAsset();
32
+ }
33
+
34
+ Object.defineProperty(resolveAssetSource, 'setCustomSourceTransformer', {
35
+ get() {
36
+ return setCustomSourceTransformer;
37
+ },
38
+ });
39
+
40
+ export const { pickScale } = AssetSourceResolver;
@@ -0,0 +1,36 @@
1
+ import { ImageSource } from '../Image.types';
2
+
3
+ type ImageHashType = 'blurhash' | 'thumbhash';
4
+
5
+ function hashToUri(type: ImageHashType, hash: string): string {
6
+ const encodedBlurhash = encodeURI(hash).replace(/#/g, '%23').replace(/\?/g, '%3F');
7
+ return `${type}:/${encodedBlurhash}`;
8
+ }
9
+
10
+ /**
11
+ * Converts a blurhash string (`blurhash:/<hash>/<width>/<height>` or <hash>/<width>/<height>) into an `ImageSource`.
12
+ *
13
+ * @return An ImageSource representing the provided blurhash.
14
+ * */
15
+ export function resolveBlurhashString(str: string): ImageSource {
16
+ const [blurhash, width, height] = str.replace(/^blurhash:\//, '').split('/');
17
+ return {
18
+ uri: hashToUri('blurhash', blurhash),
19
+ width: parseInt(width, 10) || 16,
20
+ height: parseInt(height, 10) || 16,
21
+ };
22
+ }
23
+
24
+ /**
25
+ * Converts a thumbhash string (`thumbhash:/<hash>` or `<hash>`) into an `ImageSource`.
26
+ *
27
+ * @return An ImageSource representing the provided thumbhash.
28
+ * */
29
+ export function resolveThumbhashString(str: string): ImageSource {
30
+ // ThumbHash may contain slashes that could break the url when the slash is at the beginning.
31
+ // We replace slashes with backslashes to make sure we don't break the url's path.
32
+ const thumbhash = str.replace(/^thumbhash:\//, '').replace(/\//g, '\\');
33
+ return {
34
+ uri: hashToUri('thumbhash', thumbhash),
35
+ };
36
+ }
@@ -0,0 +1,33 @@
1
+ import { ImageSource } from '../Image.types';
2
+
3
+ /**
4
+ * Converts a string in blurhash format (`blurhash:/<hash>/<width>/<height>`
5
+ * or <hash>/<width>/<height>) into an `ImageSource`.
6
+ * Note: The blurhash:/ uri scheme is removed, because for backward compatibility reasons,
7
+ * strings without a scheme are assumed to be `blurhash` by default.
8
+ *
9
+ * @return An ImageSource representing the provided blurhash.
10
+ * */
11
+ export function resolveBlurhashString(str: string): ImageSource {
12
+ const [blurhash, width, height] = str.replace(/^blurhash:\//, '').split('/');
13
+ return {
14
+ uri: blurhash,
15
+ width: parseInt(width, 10) || 16,
16
+ height: parseInt(height, 10) || 16,
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Converts a string in thumbhash format (`thumbhash:/<hash>` or `<hash>`)
22
+ * into an `ImageSource`.
23
+ * Note: Unlike the `resolveBlurhashString` the `thumbhash:/` scheme has to be present,
24
+ * as the scheme has to be explicitly stated to be interpreted a `thumbhash` source.
25
+ *
26
+ * @return An ImageSource representing the provided thumbhash.
27
+ * */
28
+ export function resolveThumbhashString(str: string): ImageSource {
29
+ const hash = str.replace(/^thumbhash:\//, '');
30
+ return {
31
+ uri: 'thumbhash:/' + hash,
32
+ };
33
+ }
@@ -0,0 +1,48 @@
1
+ import resolveAssetSource from './resolveAssetSource';
2
+ import { resolveBlurhashString, resolveThumbhashString } from './resolveHashString';
3
+ import { ImageNativeProps, ImageProps, ImageSource } from '../Image.types';
4
+
5
+ export function isBlurhashString(str: string): boolean {
6
+ return /^(blurhash:\/)?[\w#$%*+,\-.:;=?@[\]^_{}|~]+(\/[\d.]+)*$/.test(str);
7
+ }
8
+
9
+ // Base64 strings will be recognized as blurhash by default (to keep compatibility),
10
+ // interpret as thumbhash only if correct uri scheme is provided
11
+ export function isThumbhashString(str: string): boolean {
12
+ return str.startsWith('thumbhash:/');
13
+ }
14
+
15
+ function resolveSource(source?: ImageSource | string | number | null): ImageSource | null {
16
+ if (typeof source === 'string') {
17
+ if (isBlurhashString(source)) {
18
+ return resolveBlurhashString(source);
19
+ } else if (isThumbhashString(source)) {
20
+ return resolveThumbhashString(source);
21
+ }
22
+ return { uri: source };
23
+ }
24
+ if (typeof source === 'number') {
25
+ return resolveAssetSource(source);
26
+ }
27
+ if (typeof source === 'object' && (source?.blurhash || source?.thumbhash)) {
28
+ const { blurhash, thumbhash, ...restSource } = source;
29
+ const resolved = thumbhash
30
+ ? resolveThumbhashString(thumbhash)
31
+ : resolveBlurhashString(blurhash as string);
32
+ return {
33
+ ...resolved,
34
+ ...restSource,
35
+ };
36
+ }
37
+ return source ?? null;
38
+ }
39
+
40
+ /**
41
+ * Resolves provided `source` prop to an array of objects expected by the native implementation.
42
+ */
43
+ export function resolveSources(sources?: ImageProps['source']): ImageNativeProps['source'] {
44
+ if (Array.isArray(sources)) {
45
+ return sources.map(resolveSource).filter(Boolean) as ImageSource[];
46
+ }
47
+ return [resolveSource(sources)].filter(Boolean) as ImageSource[];
48
+ }