@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,326 @@
1
+ // The blurhash algorithm was entirely created by Wolt Enterprises.
2
+ // This implementation was inspired by:
3
+ // - https://github.com/woltapp/blurhash/blob/master/Swift/BlurHashDecode.swift
4
+ // - https://github.com/woltapp/blurhash/blob/master/Swift/BlurHashEncode.swift
5
+ // See https://blurha.sh for more details about the blurhash.
6
+
7
+ import UIKit
8
+
9
+ // swiftlint:disable force_unwrapping
10
+
11
+ internal func image(fromBlurhash blurhash: String, size: CGSize, punch: Float = 1.0) -> CGImage? {
12
+ guard blurhash.count >= 6 else {
13
+ return nil
14
+ }
15
+
16
+ let sizeFlag = decode83(String(blurhash[0]))
17
+ let numY = (sizeFlag / 9) + 1
18
+ let numX = (sizeFlag % 9) + 1
19
+
20
+ let quantisedMaximumValue = decode83(String(blurhash[1]))
21
+ let maximumValue = Float(quantisedMaximumValue + 1) / 166
22
+
23
+ guard blurhash.count == 4 + 2 * numX * numY else {
24
+ return nil
25
+ }
26
+
27
+ let colors: [(Float, Float, Float)] = (0 ..< numX * numY).map { i in
28
+ if i == 0 {
29
+ let value = decode83(String(blurhash[2 ..< 6]))
30
+ return decodeDC(value)
31
+ } else {
32
+ let value = decode83(String(blurhash[4 + i * 2 ..< 4 + i * 2 + 2]))
33
+ return decodeAC(value, maximumValue: maximumValue * punch)
34
+ }
35
+ }
36
+
37
+ let width = Int(size.width)
38
+ let height = Int(size.height)
39
+ let bytesPerRow = width * 3
40
+
41
+ guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else {
42
+ return nil
43
+ }
44
+
45
+ CFDataSetLength(data, bytesPerRow * height)
46
+
47
+ guard let pixels = CFDataGetMutableBytePtr(data) else {
48
+ return nil
49
+ }
50
+
51
+ for y in 0 ..< height {
52
+ for x in 0 ..< width {
53
+ var r: Float = 0
54
+ var g: Float = 0
55
+ var b: Float = 0
56
+
57
+ for j in 0 ..< numY {
58
+ for i in 0 ..< numX {
59
+ let basis = cos(Float.pi * Float(x) * Float(i) / Float(width)) * cos(Float.pi * Float(y) * Float(j) / Float(height))
60
+ let color = colors[i + j * numX]
61
+ r += color.0 * basis
62
+ g += color.1 * basis
63
+ b += color.2 * basis
64
+ }
65
+ }
66
+
67
+ let intR = UInt8(linearTosRGB(r))
68
+ let intG = UInt8(linearTosRGB(g))
69
+ let intB = UInt8(linearTosRGB(b))
70
+
71
+ pixels[3 * x + 0 + y * bytesPerRow] = intR
72
+ pixels[3 * x + 1 + y * bytesPerRow] = intG
73
+ pixels[3 * x + 2 + y * bytesPerRow] = intB
74
+ }
75
+ }
76
+
77
+ let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
78
+
79
+ guard let provider = CGDataProvider(data: data), let cgImage = CGImage(
80
+ width: width,
81
+ height: height,
82
+ bitsPerComponent: 8,
83
+ bitsPerPixel: 24,
84
+ bytesPerRow: bytesPerRow,
85
+ space: CGColorSpaceCreateDeviceRGB(),
86
+ bitmapInfo: bitmapInfo,
87
+ provider: provider,
88
+ decode: nil,
89
+ shouldInterpolate: true,
90
+ intent: .defaultIntent
91
+ ) else {
92
+ return nil
93
+ }
94
+
95
+ return cgImage
96
+ }
97
+
98
+ internal func blurhash(fromImage image: UIImage, numberOfComponents components: (Int, Int)) -> String? {
99
+ let size = image.size
100
+ let scale = image.scale
101
+
102
+ let pixelWidth = Int(round(size.width * scale))
103
+ let pixelHeight = Int(round(size.height * scale))
104
+
105
+ let context = CGContext(
106
+ data: nil,
107
+ width: pixelWidth,
108
+ height: pixelHeight,
109
+ bitsPerComponent: 8,
110
+ bytesPerRow: pixelWidth * 4,
111
+ space: CGColorSpace(name: CGColorSpace.sRGB)!,
112
+ bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
113
+ )!
114
+ context.scaleBy(x: scale, y: -scale)
115
+ context.translateBy(x: 0, y: -size.height)
116
+
117
+ UIGraphicsPushContext(context)
118
+ image.draw(at: .zero)
119
+ UIGraphicsPopContext()
120
+
121
+ guard let cgImage = context.makeImage(),
122
+ let dataProvider = cgImage.dataProvider,
123
+ let data = dataProvider.data,
124
+ let pixels = CFDataGetBytePtr(data) else {
125
+ assertionFailure("Unexpected error!")
126
+ return nil
127
+ }
128
+
129
+ let width = cgImage.width
130
+ let height = cgImage.height
131
+ let bytesPerRow = cgImage.bytesPerRow
132
+
133
+ var factors: [(Float, Float, Float)] = []
134
+ for y in 0 ..< components.1 {
135
+ for x in 0 ..< components.0 {
136
+ let normalisation: Float = (x == 0 && y == 0) ? 1 : 2
137
+ let factor = multiplyBasisFunction(
138
+ pixels: pixels,
139
+ width: width,
140
+ height: height,
141
+ bytesPerRow: bytesPerRow,
142
+ bytesPerPixel: cgImage.bitsPerPixel / 8,
143
+ pixelOffset: 0
144
+ ) {
145
+ normalisation * cos(Float.pi * Float(x) * $0 / Float(width)) as Float * cos(Float.pi * Float(y) * $1 / Float(height)) as Float
146
+ }
147
+ factors.append(factor)
148
+ }
149
+ }
150
+
151
+ let dc = factors.first!
152
+ let ac = factors.dropFirst()
153
+
154
+ var hash = ""
155
+
156
+ let sizeFlag = (components.0 - 1) + (components.1 - 1) * 9
157
+ hash += encode83(sizeFlag, length: 1)
158
+
159
+ let maximumValue: Float
160
+ if !ac.isEmpty {
161
+ let actualMaximumValue = ac.map({ max(abs($0.0), abs($0.1), abs($0.2)) }).max()!
162
+ let quantisedMaximumValue = Int(max(0, min(82, floor(actualMaximumValue * 166 - 0.5))))
163
+ maximumValue = Float(quantisedMaximumValue + 1) / 166
164
+ hash += encode83(quantisedMaximumValue, length: 1)
165
+ } else {
166
+ maximumValue = 1
167
+ hash += encode83(0, length: 1)
168
+ }
169
+
170
+ hash += encode83(encodeDC(dc), length: 4)
171
+
172
+ for factor in ac {
173
+ hash += encode83(encodeAC(factor, maximumValue: maximumValue), length: 2)
174
+ }
175
+
176
+ return hash
177
+ }
178
+
179
+ internal func isBlurhash(_ str: String) -> Bool {
180
+ return str.allSatisfy { char in decodeCharacters[char] != nil }
181
+ }
182
+
183
+ // MARK: - Encode
184
+
185
+ // swiftlint:disable:next function_parameter_count
186
+ private func multiplyBasisFunction(
187
+ pixels: UnsafePointer<UInt8>,
188
+ width: Int,
189
+ height: Int,
190
+ bytesPerRow: Int,
191
+ bytesPerPixel: Int,
192
+ pixelOffset: Int,
193
+ basisFunction: (Float, Float) -> Float
194
+ ) -> (Float, Float, Float) {
195
+ var r: Float = 0
196
+ var g: Float = 0
197
+ var b: Float = 0
198
+
199
+ let buffer = UnsafeBufferPointer(start: pixels, count: height * bytesPerRow)
200
+
201
+ for x in 0 ..< width {
202
+ for y in 0 ..< height {
203
+ let basis = basisFunction(Float(x), Float(y))
204
+ r += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 0 + y * bytesPerRow])
205
+ g += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 1 + y * bytesPerRow])
206
+ b += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 2 + y * bytesPerRow])
207
+ }
208
+ }
209
+
210
+ let scale = 1 / Float(width * height)
211
+
212
+ return (r * scale, g * scale, b * scale)
213
+ }
214
+
215
+ private let encodeCharacters: [Character] = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~")
216
+
217
+ private func encodeDC(_ value: (Float, Float, Float)) -> Int {
218
+ let roundedR = linearTosRGB(value.0)
219
+ let roundedG = linearTosRGB(value.1)
220
+ let roundedB = linearTosRGB(value.2)
221
+ return (roundedR << 16) + (roundedG << 8) + roundedB
222
+ }
223
+
224
+ private func encodeAC(_ value: (Float, Float, Float), maximumValue: Float) -> Int {
225
+ let quantR = Int(max(0, min(18, floor(signPow(value.0 / maximumValue, 0.5) * 9 + 9.5))))
226
+ let quantG = Int(max(0, min(18, floor(signPow(value.1 / maximumValue, 0.5) * 9 + 9.5))))
227
+ let quantB = Int(max(0, min(18, floor(signPow(value.2 / maximumValue, 0.5) * 9 + 9.5))))
228
+
229
+ return quantR * 19 * 19 + quantG * 19 + quantB
230
+ }
231
+
232
+ private func encode83(_ value: Int, length: Int) -> String {
233
+ var result = ""
234
+ for i in 1 ... length {
235
+ let digit = (value / pow(83, length - i)) % 83
236
+ result += String(encodeCharacters[Int(digit)])
237
+ }
238
+ return result
239
+ }
240
+
241
+ // MARK: - Decode
242
+
243
+ private func decodeDC(_ value: Int) -> (Float, Float, Float) {
244
+ let intR = value >> 16
245
+ let intG = (value >> 8) & 255
246
+ let intB = value & 255
247
+ return (sRGBToLinear(intR), sRGBToLinear(intG), sRGBToLinear(intB))
248
+ }
249
+
250
+ private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float) {
251
+ let quantR = value / (19 * 19)
252
+ let quantG = (value / 19) % 19
253
+ let quantB = value % 19
254
+
255
+ let rgb = (
256
+ signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
257
+ signPow((Float(quantG) - 9) / 9, 2) * maximumValue,
258
+ signPow((Float(quantB) - 9) / 9, 2) * maximumValue
259
+ )
260
+
261
+ return rgb
262
+ }
263
+
264
+ private let decodeCharacters: [Character: Int] = {
265
+ var dict: [Character: Int] = [:]
266
+ for (index, character) in encodeCharacters.enumerated() {
267
+ dict[character] = index
268
+ }
269
+ return dict
270
+ }()
271
+
272
+ private func decode83(_ str: String) -> Int {
273
+ var value: Int = 0
274
+ for character in str {
275
+ if let digit = decodeCharacters[character] {
276
+ value = value * 83 + digit
277
+ }
278
+ }
279
+ return value
280
+ }
281
+
282
+ // MARK: - Helpers
283
+
284
+ private func signPow(_ value: Float, _ exp: Float) -> Float {
285
+ return copysign(pow(abs(value), exp), value)
286
+ }
287
+
288
+ private func linearTosRGB(_ value: Float) -> Int {
289
+ let v = max(0, min(1, value))
290
+ if v <= 0.0031308 {
291
+ return Int(v * 12.92 * 255 + 0.5)
292
+ } else {
293
+ return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5)
294
+ }
295
+ }
296
+
297
+ private func sRGBToLinear<Type: BinaryInteger>(_ value: Type) -> Float {
298
+ let v = Float(Int64(value)) / 255
299
+ if v <= 0.04045 {
300
+ return v / 12.92
301
+ } else {
302
+ return pow((v + 0.055) / 1.055, 2.4)
303
+ }
304
+ }
305
+
306
+ private func pow(_ base: Int, _ exponent: Int) -> Int {
307
+ return (0 ..< exponent).reduce(1) { value, _ in value * base }
308
+ }
309
+
310
+ private extension String {
311
+ subscript (offset: Int) -> Character {
312
+ return self[index(startIndex, offsetBy: offset)]
313
+ }
314
+
315
+ subscript (bounds: CountableClosedRange<Int>) -> Substring {
316
+ let start = index(startIndex, offsetBy: bounds.lowerBound)
317
+ let end = index(startIndex, offsetBy: bounds.upperBound)
318
+ return self[start...end]
319
+ }
320
+
321
+ subscript (bounds: CountableRange<Int>) -> Substring {
322
+ let start = index(startIndex, offsetBy: bounds.lowerBound)
323
+ let end = index(startIndex, offsetBy: bounds.upperBound)
324
+ return self[start..<end]
325
+ }
326
+ }
@@ -0,0 +1,52 @@
1
+ import SDWebImage
2
+ import ExpoModulesCore
3
+
4
+ class BlurhashLoader: NSObject, SDImageLoader {
5
+ // MARK: - SDImageLoader
6
+
7
+ func canRequestImage(for url: URL?) -> Bool {
8
+ return url?.scheme == "blurhash"
9
+ }
10
+
11
+ func requestImage(
12
+ with url: URL?,
13
+ options: SDWebImageOptions = [],
14
+ context: [SDWebImageContextOption: Any]?,
15
+ progress progressBlock: SDImageLoaderProgressBlock?,
16
+ completed completedBlock: SDImageLoaderCompletedBlock? = nil
17
+ ) -> SDWebImageOperation? {
18
+ guard let url = url else {
19
+ let error = makeNSError(description: "URL provided to BlurhashLoader is missing")
20
+ completedBlock?(nil, nil, error, false)
21
+ return nil
22
+ }
23
+ guard let source = context?[ImageView.contextSourceKey] as? ImageSource else {
24
+ let error = makeNSError(description: "Image source was not provided to the context")
25
+ completedBlock?(nil, nil, error, false)
26
+ return nil
27
+ }
28
+
29
+ // The URI looks like this: blurhash:/WgF}G?az0fs.x[jat7xFRjNHt6s.4;oe-:RkVtkCi^Nbo|xZRjWB
30
+ // Which means that the `pathComponents[0]` is `/` and we need to skip it to get the hash.
31
+ let blurhash = url.pathComponents[1]
32
+ let size = CGSize(width: source.width, height: source.height)
33
+
34
+ DispatchQueue.global(qos: .userInitiated).async {
35
+ if let image = image(fromBlurhash: blurhash, size: size) {
36
+ DispatchQueue.main.async {
37
+ completedBlock?(UIImage(cgImage: image), nil, nil, true)
38
+ }
39
+ } else {
40
+ let error = makeNSError(description: "Unable to generate an image from the given blurhash")
41
+ completedBlock?(nil, nil, error, false)
42
+ }
43
+ }
44
+ return nil
45
+ }
46
+
47
+ func shouldBlockFailedURL(with url: URL, error: Error) -> Bool {
48
+ // If the algorithm failed to generate an image from the url,
49
+ // it's not possible that next time it will work :)
50
+ return true
51
+ }
52
+ }
@@ -0,0 +1,55 @@
1
+ import ExpoModulesCore
2
+
3
+ /**
4
+ Describes how the image should be resized to fit its container.
5
+ - Note: It mirrors the CSS [`object-fit`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) property.
6
+ */
7
+ enum ContentFit: String, Enumerable {
8
+ /**
9
+ The image is scaled to maintain its aspect ratio while fitting within the container's box.
10
+ The entire image is made to fill the box, while preserving its aspect ratio,
11
+ so the image will be "letterboxed" if its aspect ratio does not match the aspect ratio of the box.
12
+ */
13
+ case contain
14
+
15
+ /**
16
+ The image is sized to maintain its aspect ratio while filling the element's entire content box.
17
+ If the image's aspect ratio does not match the aspect ratio of its box, then the object will be clipped to fit.
18
+ */
19
+ case cover
20
+
21
+ /**
22
+ The image is sized to fill the element's content box. The entire object will completely fill the box.
23
+ If the image's aspect ratio does not match the aspect ratio of its box, then the image will be stretched to fit.
24
+ */
25
+ case fill
26
+
27
+ /**
28
+ The image is not resized and is centered by default.
29
+ When specified, the exact position can be controlled with `ContentPosition`.
30
+ */
31
+ case none
32
+
33
+ /**
34
+ The image is sized as if `none` or `contain` were specified,
35
+ whichever would result in a smaller concrete image size.
36
+ */
37
+ case scaleDown = "scale-down"
38
+
39
+ /**
40
+ `ContentFit` cases can be directly translated to the native `UIView.ContentMode`
41
+ except `scaleDown` that needs to be handled differently at the later step of rendering.
42
+ */
43
+ func toContentMode() -> UIView.ContentMode {
44
+ switch self {
45
+ case .contain:
46
+ return .scaleAspectFit
47
+ case .cover:
48
+ return .scaleAspectFill
49
+ case .fill:
50
+ return .scaleToFill
51
+ case .none, .scaleDown:
52
+ return .center
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,108 @@
1
+ import ExpoModulesCore
2
+
3
+ /**
4
+ Represents a position value that might be either `Double` or `String`.
5
+ */
6
+ typealias ContentPositionValue = Either<Double, String>
7
+
8
+ /**
9
+ Specifies the alignment of the image within the container's box.
10
+ - Note: Its intention is to behave like the CSS [`object-position`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) property.
11
+ */
12
+ struct ContentPosition: Record {
13
+ static let center = Self()
14
+
15
+ @Field
16
+ var top: ContentPositionValue?
17
+
18
+ @Field
19
+ var bottom: ContentPositionValue?
20
+
21
+ @Field
22
+ var right: ContentPositionValue?
23
+
24
+ @Field
25
+ var left: ContentPositionValue?
26
+
27
+ /**
28
+ Returns a horizontal content offset based on the `left` or `right` field.
29
+ */
30
+ func offsetX(contentWidth: Double, containerWidth: Double) -> Double {
31
+ let diff = containerWidth - contentWidth
32
+
33
+ if let leftDistance = distance(from: left) {
34
+ return -diff / 2 + leftDistance
35
+ }
36
+ if let rightDistance = distance(from: right) {
37
+ return diff / 2 - rightDistance
38
+ }
39
+ if let factor = factor(from: left) {
40
+ return -diff / 2 + diff * factor
41
+ }
42
+ if let factor = factor(from: right) {
43
+ return diff / 2 - diff * factor
44
+ }
45
+ return 0
46
+ }
47
+
48
+ /**
49
+ Returns a vertical content offset based on the `top` or `bottom` field.
50
+ */
51
+ func offsetY(contentHeight: Double, containerHeight: Double) -> Double {
52
+ let diff = containerHeight - contentHeight
53
+
54
+ if let topDistance = distance(from: top) {
55
+ return -diff / 2 + topDistance
56
+ }
57
+ if let bottomDistance = distance(from: bottom) {
58
+ return diff / 2 - bottomDistance
59
+ }
60
+ if let factor = factor(from: top) {
61
+ return -diff / 2 + diff * factor
62
+ }
63
+ if let factor = factor(from: bottom) {
64
+ return diff / 2 - diff * factor
65
+ }
66
+ return 0
67
+ }
68
+
69
+ /**
70
+ A `CGPoint` with horizontal and vertical content offsets.
71
+ */
72
+ func offset(contentSize: CGSize, containerSize: CGSize) -> CGPoint {
73
+ return CGPoint(
74
+ x: offsetX(contentWidth: contentSize.width, containerWidth: containerSize.width),
75
+ y: offsetY(contentHeight: contentSize.height, containerHeight: containerSize.height)
76
+ )
77
+ }
78
+ }
79
+
80
+ /**
81
+ Returns a static offset from the given position value or `nil` when it cannot be cast to a `Double`.
82
+ */
83
+ private func distance(from value: ContentPositionValue?) -> Double? {
84
+ if let value: Double = value?.get() {
85
+ return value
86
+ }
87
+ if let value: String = value?.get() {
88
+ return Double(value)
89
+ }
90
+ return nil
91
+ }
92
+
93
+ /**
94
+ Returns a factor from the percentage value from the given position.
95
+ The value must be a string containing a number and `%` character, or equal to `"center"` which is an equivalent to `50%`.
96
+ */
97
+ private func factor(from value: ContentPositionValue?) -> Double? {
98
+ guard let value: String = value?.get() else {
99
+ return nil
100
+ }
101
+ if value == "center" {
102
+ return 0.5
103
+ }
104
+ guard value.contains("%"), let percentage = Double(value.replacingOccurrences(of: "%", with: "")) else {
105
+ return nil
106
+ }
107
+ return percentage / 100
108
+ }
@@ -0,0 +1,30 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'ExpoImage'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = package['description']
10
+ s.license = package['license']
11
+ s.author = package['author']
12
+ s.homepage = package['homepage']
13
+ s.platform = :ios, '13.0'
14
+ s.swift_version = '5.4'
15
+ s.source = { git: 'https://github.com/expo/expo.git' }
16
+ s.static_framework = true
17
+
18
+ s.dependency 'ExpoModulesCore'
19
+ s.dependency 'SDWebImage', '~> 5.17.0'
20
+ s.dependency 'SDWebImageWebPCoder', '~> 0.13.0'
21
+ s.dependency 'SDWebImageAVIFCoder', '~> 0.10.1'
22
+ s.dependency 'SDWebImageSVGCoder', '~> 1.7.0'
23
+
24
+ # Swift/Objective-C compatibility
25
+ s.pod_target_xcconfig = {
26
+ 'DEFINES_MODULE' => 'YES',
27
+ }
28
+
29
+ s.source_files = "**/*.{h,m,swift}"
30
+ end
@@ -0,0 +1,24 @@
1
+ // Copyright 2022-present 650 Industries. All rights reserved.
2
+
3
+ import SDWebImage
4
+ import ExpoModulesCore
5
+
6
+ enum ImageCachePolicy: String, Enumerable {
7
+ case none = "none"
8
+ case disk = "disk"
9
+ case memory = "memory"
10
+ case memoryAndDisk = "memory-disk"
11
+
12
+ func toSdCacheType() -> SDImageCacheType {
13
+ switch self {
14
+ case .none:
15
+ return .none
16
+ case .disk:
17
+ return .disk
18
+ case .memory:
19
+ return .memory
20
+ case .memoryAndDisk:
21
+ return .all
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,21 @@
1
+ // Copyright 2022-present 650 Industries. All rights reserved.
2
+
3
+ import SDWebImage
4
+ import ExpoModulesCore
5
+
6
+ enum ImageCacheType: String, Enumerable {
7
+ case none
8
+ case disk
9
+ case memory
10
+
11
+ static func fromSdCacheType(_ sdImageCacheType: SDImageCacheType) -> ImageCacheType {
12
+ switch sdImageCacheType {
13
+ case .none:
14
+ return .none
15
+ case .disk, .all:
16
+ return .disk
17
+ case .memory:
18
+ return .memory
19
+ }
20
+ }
21
+ }