From d88187be16bc30c3a1533da6d58fb43d0b1b7105 Mon Sep 17 00:00:00 2001 From: orfelorfel23 Date: Sun, 31 May 2026 20:50:37 +0200 Subject: [PATCH] update: latest source changes --- node_modules/.package-lock.json | 17 + node_modules/expo-image/.eslintrc.js | 2 + node_modules/expo-image/CHANGELOG.md | 770 ++++++++++++++++++ node_modules/expo-image/README.md | 64 ++ node_modules/expo-image/android/build.gradle | 45 + .../expo-image/android/proguard-rules.pro | 28 + .../android/src/main/AndroidManifest.xml | 17 + .../java/com/caverock/androidsvg/SVGStyler.kt | 125 +++ .../modules/image/CustomDownsampleStrategy.kt | 226 +++++ .../java/expo/modules/image/CustomOptions.kt | 8 + .../java/expo/modules/image/Exceptions.kt | 6 + .../modules/image/ExpoImageAppGlideModule.kt | 26 + .../image/ExpoImageComponentCallbacks.kt | 22 + .../expo/modules/image/ExpoImageModule.kt | 341 ++++++++ .../java/expo/modules/image/ExpoImageView.kt | 129 +++ .../modules/image/ExpoImageViewWrapper.kt | 647 +++++++++++++++ .../expo/modules/image/GlideExtensions.kt | 67 ++ .../expo/modules/image/GlideModelProvider.kt | 51 ++ .../src/main/java/expo/modules/image/Image.kt | 20 + .../java/expo/modules/image/ImageLoadTask.kt | 39 + .../java/expo/modules/image/ImageUtils.kt | 42 + .../modules/image/ImageViewWrapperTarget.kt | 350 ++++++++ .../expo/modules/image/ResourceIdHelper.kt | 47 ++ .../ThumbnailRequestCoordinatorExtension.kt | 21 + .../src/main/java/expo/modules/image/Trace.kt | 11 + .../main/java/expo/modules/image/YogaUtils.kt | 20 + .../modules/image/blurhash/BlurHashFetcher.kt | 32 + .../modules/image/blurhash/BlurhashDecoder.kt | 180 ++++ .../modules/image/blurhash/BlurhashEncoder.kt | 112 +++ .../modules/image/blurhash/BlurhashHelpers.kt | 38 + .../modules/image/blurhash/BlurhashModel.kt | 9 + .../image/blurhash/BlurhashModelLoader.kt | 29 + .../blurhash/BlurhashModelLoaderFactory.kt | 13 + .../modules/image/blurhash/BlurhashModule.kt | 16 + .../image/dataurls/Base64DataFetcher.kt | 27 + .../image/dataurls/Base64ModelLoader.kt | 24 + .../dataurls/Base64ModelLoaderFactory.kt | 12 + .../modules/image/dataurls/Base64Module.kt | 16 + .../image/decodedsource/DecodedFetcher.kt | 19 + .../image/decodedsource/DecodedModel.kt | 5 + .../image/decodedsource/DecodedModelLoader.kt | 18 + .../DecodedModelLoaderFactory.kt | 13 + .../image/decodedsource/DecodedModule.kt | 16 + .../modules/image/drawing/OutlineProvider.kt | 218 +++++ .../expo/modules/image/enums/ContentFit.kt | 89 ++ .../modules/image/enums/ImageCacheType.kt | 14 + .../java/expo/modules/image/enums/Priority.kt | 15 + .../image/events/GlideRequestListener.kt | 77 ++ .../image/events/OkHttpProgressListener.kt | 26 + .../ExpoImageOkHttpClientGlideModule.kt | 80 ++ .../image/okhttp/GlideUrlWrapperLoader.kt | 55 ++ .../expo/modules/image/records/CachePolicy.kt | 10 + .../modules/image/records/ContentPosition.kt | 89 ++ .../modules/image/records/DecodeFormat.kt | 22 + .../modules/image/records/ImageLoadOptions.kt | 12 + .../modules/image/records/ImageTransition.kt | 8 + .../expo/modules/image/records/SourceMap.kt | 192 +++++ .../java/expo/modules/image/records/events.kt | 26 + .../java/expo/modules/image/svg/SVGDecoder.kt | 42 + .../image/svg/SVGDrawableTranscoder.kt | 49 ++ .../java/expo/modules/image/svg/SVGModule.kt | 26 + .../image/thumbhash/ThumbhashDecoder.kt | 222 +++++ .../image/thumbhash/ThumbhashEncoder.kt | 184 +++++ .../image/thumbhash/ThumbhashFetcher.kt | 31 + .../modules/image/thumbhash/ThumbhashModel.kt | 7 + .../image/thumbhash/ThumbhashModelLoader.kt | 24 + .../thumbhash/ThumbhashModelLoaderFactory.kt | 13 + .../image/thumbhash/ThumbhashModule.kt | 16 + .../expo-image/expo-module.config.json | 15 + .../expo-image/ios/AnimatedImage.swift | 37 + node_modules/expo-image/ios/Blurhash.swift | 326 ++++++++ .../expo-image/ios/BlurhashLoader.swift | 52 ++ node_modules/expo-image/ios/ContentFit.swift | 55 ++ .../expo-image/ios/ContentPosition.swift | 108 +++ node_modules/expo-image/ios/ExpoImage.podspec | 41 + node_modules/expo-image/ios/Image.swift | 20 + .../expo-image/ios/ImageCachePolicy.swift | 24 + .../expo-image/ios/ImageCacheType.swift | 24 + .../expo-image/ios/ImageLoadOptions.swift | 12 + .../expo-image/ios/ImageLoadTask.swift | 27 + node_modules/expo-image/ios/ImageLoader.swift | 39 + node_modules/expo-image/ios/ImageModule.swift | 264 ++++++ .../expo-image/ios/ImagePriority.swift | 22 + node_modules/expo-image/ios/ImageSource.swift | 43 + .../expo-image/ios/ImageTransition.swift | 67 ++ node_modules/expo-image/ios/ImageUtils.swift | 265 ++++++ node_modules/expo-image/ios/ImageView.swift | 542 ++++++++++++ .../ios/Loaders/PhotoLibraryAssetLoader.swift | 153 ++++ .../ios/Tests/ImageResizingSpec.swift | 130 +++ node_modules/expo-image/ios/Thumbhash.swift | 550 +++++++++++++ .../expo-image/ios/ThumbhashLoader.swift | 52 ++ node_modules/expo-image/ios/WebPCoder.swift | 73 ++ node_modules/expo-image/jest-rsc.config.js | 1 + .../expo.modules.image-3.0.11-sources.jar | Bin 0 -> 67772 bytes .../expo.modules.image-3.0.11-sources.jar.md5 | 1 + ...expo.modules.image-3.0.11-sources.jar.sha1 | 1 + ...po.modules.image-3.0.11-sources.jar.sha256 | 1 + ...po.modules.image-3.0.11-sources.jar.sha512 | 1 + .../3.0.11/expo.modules.image-3.0.11.aar | Bin 0 -> 304446 bytes .../3.0.11/expo.modules.image-3.0.11.aar.md5 | 1 + .../3.0.11/expo.modules.image-3.0.11.aar.sha1 | 1 + .../expo.modules.image-3.0.11.aar.sha256 | 1 + .../expo.modules.image-3.0.11.aar.sha512 | 1 + .../3.0.11/expo.modules.image-3.0.11.module | 177 ++++ .../expo.modules.image-3.0.11.module.md5 | 1 + .../expo.modules.image-3.0.11.module.sha1 | 1 + .../expo.modules.image-3.0.11.module.sha256 | 1 + .../expo.modules.image-3.0.11.module.sha512 | 1 + .../3.0.11/expo.modules.image-3.0.11.pom | 88 ++ .../3.0.11/expo.modules.image-3.0.11.pom.md5 | 1 + .../3.0.11/expo.modules.image-3.0.11.pom.sha1 | 1 + .../expo.modules.image-3.0.11.pom.sha256 | 1 + .../expo.modules.image-3.0.11.pom.sha512 | 1 + .../expo.modules.image/maven-metadata.xml | 13 + .../expo.modules.image/maven-metadata.xml.md5 | 1 + .../maven-metadata.xml.sha1 | 1 + .../maven-metadata.xml.sha256 | 1 + .../maven-metadata.xml.sha512 | 1 + node_modules/expo-image/package.json | 49 ++ node_modules/expo-image/src/ExpoImage.tsx | 124 +++ node_modules/expo-image/src/ExpoImage.web.tsx | 172 ++++ node_modules/expo-image/src/Image.tsx | 262 ++++++ node_modules/expo-image/src/Image.types.ts | 613 ++++++++++++++ .../expo-image/src/ImageBackground.tsx | 16 + node_modules/expo-image/src/ImageModule.ts | 5 + .../expo-image/src/ImageModule.web.ts | 76 ++ node_modules/expo-image/src/index.ts | 4 + .../ts-declarations/react-native-assets.d.ts | 1 + .../src/ts-declarations/react-native-web.d.ts | 3 + node_modules/expo-image/src/useImage.ts | 92 +++ node_modules/expo-image/src/utils.ts | 126 +++ .../src/utils/AssetSourceResolver.web.ts | 88 ++ .../expo-image/src/utils/blurhash/base83.ts | 104 +++ .../expo-image/src/utils/blurhash/decode.ts | 114 +++ .../expo-image/src/utils/blurhash/error.ts | 7 + .../src/utils/blurhash/useBlurhash.tsx | 85 ++ .../expo-image/src/utils/blurhash/utils.ts | 21 + .../src/utils/resolveAssetSource.tsx | 3 + .../src/utils/resolveAssetSource.web.ts | 40 + .../src/utils/resolveHashString.tsx | 36 + .../src/utils/resolveHashString.web.tsx | 31 + .../expo-image/src/utils/resolveSources.tsx | 58 ++ .../src/utils/thumbhash/thumbhash.ts | 387 +++++++++ .../expo-image/src/web/AnimationManager.tsx | 231 ++++++ .../expo-image/src/web/ColorTintFilter.tsx | 36 + node_modules/expo-image/src/web/ImageRef.ts | 24 + .../expo-image/src/web/ImageWrapper.tsx | 94 +++ .../expo-image/src/web/ImageWrapper.types.ts | 38 + .../src/web/getImageWrapperEventHandler.ts | 31 + node_modules/expo-image/src/web/hooks.ts | 76 ++ .../expo-image/src/web/imageStyles.tsx | 136 ++++ .../expo-image/src/web/positioning.ts | 48 ++ .../expo-image/src/web/useSourceSelection.ts | 134 +++ node_modules/expo-image/tsconfig.json | 9 + src/screens/CameraScreen.tsx | 6 +- src/screens/PreviewScreen.tsx | 2 +- 156 files changed, 11885 insertions(+), 4 deletions(-) create mode 100644 node_modules/expo-image/.eslintrc.js create mode 100644 node_modules/expo-image/CHANGELOG.md create mode 100644 node_modules/expo-image/README.md create mode 100644 node_modules/expo-image/android/build.gradle create mode 100644 node_modules/expo-image/android/proguard-rules.pro create mode 100644 node_modules/expo-image/android/src/main/AndroidManifest.xml create mode 100644 node_modules/expo-image/android/src/main/java/com/caverock/androidsvg/SVGStyler.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/CustomDownsampleStrategy.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/CustomOptions.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/Exceptions.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageAppGlideModule.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageComponentCallbacks.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageModule.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageView.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageViewWrapper.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/GlideExtensions.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/GlideModelProvider.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/Image.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/ImageLoadTask.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/ImageUtils.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/ImageViewWrapperTarget.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/ResourceIdHelper.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/ThumbnailRequestCoordinatorExtension.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/Trace.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/YogaUtils.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurHashFetcher.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashDecoder.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashEncoder.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashHelpers.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModel.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoader.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoaderFactory.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModule.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64DataFetcher.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoader.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoaderFactory.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64Module.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedFetcher.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModel.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModelLoader.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModelLoaderFactory.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModule.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/drawing/OutlineProvider.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/enums/ContentFit.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/enums/ImageCacheType.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/enums/Priority.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/events/GlideRequestListener.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/events/OkHttpProgressListener.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/okhttp/ExpoImageOkHttpClientGlideModule.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/okhttp/GlideUrlWrapperLoader.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/records/CachePolicy.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/records/ContentPosition.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/records/DecodeFormat.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/records/ImageLoadOptions.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/records/ImageTransition.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/records/SourceMap.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/records/events.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/svg/SVGDecoder.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/svg/SVGDrawableTranscoder.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/svg/SVGModule.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashDecoder.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashEncoder.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashFetcher.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModel.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoader.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoaderFactory.kt create mode 100644 node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModule.kt create mode 100644 node_modules/expo-image/expo-module.config.json create mode 100644 node_modules/expo-image/ios/AnimatedImage.swift create mode 100644 node_modules/expo-image/ios/Blurhash.swift create mode 100644 node_modules/expo-image/ios/BlurhashLoader.swift create mode 100644 node_modules/expo-image/ios/ContentFit.swift create mode 100644 node_modules/expo-image/ios/ContentPosition.swift create mode 100644 node_modules/expo-image/ios/ExpoImage.podspec create mode 100644 node_modules/expo-image/ios/Image.swift create mode 100644 node_modules/expo-image/ios/ImageCachePolicy.swift create mode 100644 node_modules/expo-image/ios/ImageCacheType.swift create mode 100644 node_modules/expo-image/ios/ImageLoadOptions.swift create mode 100644 node_modules/expo-image/ios/ImageLoadTask.swift create mode 100644 node_modules/expo-image/ios/ImageLoader.swift create mode 100644 node_modules/expo-image/ios/ImageModule.swift create mode 100644 node_modules/expo-image/ios/ImagePriority.swift create mode 100644 node_modules/expo-image/ios/ImageSource.swift create mode 100644 node_modules/expo-image/ios/ImageTransition.swift create mode 100644 node_modules/expo-image/ios/ImageUtils.swift create mode 100644 node_modules/expo-image/ios/ImageView.swift create mode 100644 node_modules/expo-image/ios/Loaders/PhotoLibraryAssetLoader.swift create mode 100644 node_modules/expo-image/ios/Tests/ImageResizingSpec.swift create mode 100644 node_modules/expo-image/ios/Thumbhash.swift create mode 100644 node_modules/expo-image/ios/ThumbhashLoader.swift create mode 100644 node_modules/expo-image/ios/WebPCoder.swift create mode 100644 node_modules/expo-image/jest-rsc.config.js create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.md5 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.sha1 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.sha256 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.sha512 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.md5 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.sha1 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.sha256 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.sha512 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.md5 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.sha1 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.sha256 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.sha512 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.md5 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.sha1 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.sha256 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.sha512 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.md5 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha1 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha256 create mode 100644 node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha512 create mode 100644 node_modules/expo-image/package.json create mode 100644 node_modules/expo-image/src/ExpoImage.tsx create mode 100644 node_modules/expo-image/src/ExpoImage.web.tsx create mode 100644 node_modules/expo-image/src/Image.tsx create mode 100644 node_modules/expo-image/src/Image.types.ts create mode 100644 node_modules/expo-image/src/ImageBackground.tsx create mode 100644 node_modules/expo-image/src/ImageModule.ts create mode 100644 node_modules/expo-image/src/ImageModule.web.ts create mode 100644 node_modules/expo-image/src/index.ts create mode 100644 node_modules/expo-image/src/ts-declarations/react-native-assets.d.ts create mode 100644 node_modules/expo-image/src/ts-declarations/react-native-web.d.ts create mode 100644 node_modules/expo-image/src/useImage.ts create mode 100644 node_modules/expo-image/src/utils.ts create mode 100644 node_modules/expo-image/src/utils/AssetSourceResolver.web.ts create mode 100644 node_modules/expo-image/src/utils/blurhash/base83.ts create mode 100644 node_modules/expo-image/src/utils/blurhash/decode.ts create mode 100644 node_modules/expo-image/src/utils/blurhash/error.ts create mode 100644 node_modules/expo-image/src/utils/blurhash/useBlurhash.tsx create mode 100644 node_modules/expo-image/src/utils/blurhash/utils.ts create mode 100644 node_modules/expo-image/src/utils/resolveAssetSource.tsx create mode 100644 node_modules/expo-image/src/utils/resolveAssetSource.web.ts create mode 100644 node_modules/expo-image/src/utils/resolveHashString.tsx create mode 100644 node_modules/expo-image/src/utils/resolveHashString.web.tsx create mode 100644 node_modules/expo-image/src/utils/resolveSources.tsx create mode 100644 node_modules/expo-image/src/utils/thumbhash/thumbhash.ts create mode 100644 node_modules/expo-image/src/web/AnimationManager.tsx create mode 100644 node_modules/expo-image/src/web/ColorTintFilter.tsx create mode 100644 node_modules/expo-image/src/web/ImageRef.ts create mode 100644 node_modules/expo-image/src/web/ImageWrapper.tsx create mode 100644 node_modules/expo-image/src/web/ImageWrapper.types.ts create mode 100644 node_modules/expo-image/src/web/getImageWrapperEventHandler.ts create mode 100644 node_modules/expo-image/src/web/hooks.ts create mode 100644 node_modules/expo-image/src/web/imageStyles.tsx create mode 100644 node_modules/expo-image/src/web/positioning.ts create mode 100644 node_modules/expo-image/src/web/useSourceSelection.ts create mode 100644 node_modules/expo-image/tsconfig.json diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 55c41fdb..a2c1457c 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -4510,6 +4510,23 @@ "react-native": "*" } }, + "node_modules/expo-image": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/expo-image/-/expo-image-3.0.11.tgz", + "integrity": "sha512-4TudfUCLgYgENv+f48omnU8tjS2S0Pd9EaON5/s1ZUBRwZ7K8acEr4NfvLPSaeXvxW24iLAiyQ7sV7BXQH3RoA==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*", + "react-native-web": "*" + }, + "peerDependenciesMeta": { + "react-native-web": { + "optional": true + } + } + }, "node_modules/expo-image-loader": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-6.0.0.tgz", diff --git a/node_modules/expo-image/.eslintrc.js b/node_modules/expo-image/.eslintrc.js new file mode 100644 index 00000000..27201978 --- /dev/null +++ b/node_modules/expo-image/.eslintrc.js @@ -0,0 +1,2 @@ +// @generated by expo-module-scripts +module.exports = require('expo-module-scripts/eslintrc.base.js'); diff --git a/node_modules/expo-image/CHANGELOG.md b/node_modules/expo-image/CHANGELOG.md new file mode 100644 index 00000000..81baf7f3 --- /dev/null +++ b/node_modules/expo-image/CHANGELOG.md @@ -0,0 +1,770 @@ +# Changelog + +## Unpublished + +### ๐Ÿ›  Breaking changes + +### ๐ŸŽ‰ New features + +### ๐Ÿ› Bug fixes + +### ๐Ÿ’ก Others + +## 3.0.11 โ€” 2025-12-05 + +_This version does not introduce any user-facing changes._ + +## 3.0.10 โ€” 2025-10-20 + +_This version does not introduce any user-facing changes._ + +## 3.0.9 โ€” 2025-10-07 + +### ๐Ÿ› Bug fixes + +- [Android] Fixed `You can't start or clear loads in RequestListener or Target callbacks`. ([#40212](https://github.com/expo/expo/pull/40212) by [@lukmccall](https://github.com/lukmccall)) + +## 3.0.8 โ€” 2025-09-11 + +_This version does not introduce any user-facing changes._ + +## 3.0.7 โ€” 2025-09-03 + +### ๐Ÿ› Bug fixes + +- [iOS] Fix images not displaying in Material Top Tabs navigator. ([#39323](https://github.com/expo/expo/pull/39323) by [@lukmccall](https://github.com/lukmccall)) + +## 3.0.6 โ€” 2025-09-02 + +_This version does not introduce any user-facing changes._ + +## 3.0.5 โ€” 2025-08-31 + +_This version does not introduce any user-facing changes._ + +## 3.0.4 โ€” 2025-08-26 + +### ๐Ÿ› Bug fixes + +- [Android] Fixed `The method 'getResourceDrawableUri' was expected to be of type static` exception. ([#39143](https://github.com/expo/expo/pull/39143) by [@lukmccall](https://github.com/lukmccall)) + +## 3.0.3 โ€” 2025-08-25 + +_This version does not introduce any user-facing changes._ + +## 3.0.2 โ€” 2025-08-16 + +_This version does not introduce any user-facing changes._ + +## 3.0.1 โ€” 2025-08-15 + +_This version does not introduce any user-facing changes._ + +## 3.0.0 โ€” 2025-08-13 + +### ๐ŸŽ‰ New features + +- Add `generateThumbhashAsync` ([#38090](https://github.com/expo/expo/pull/38090) by [@Wenszel](https://github.com/Wenszel)) +- Add support for `ImageRef` source in `generateBlurhashAsync` ([#37901](https://github.com/expo/expo/pull/37901) by [@Wenszel](https://github.com/Wenszel)) +- [Android] Add generateBlurhashAsync ([#37817](https://github.com/expo/expo/pull/37817) by [@Wenszel](https://github.com/Wenszel)) + +### ๐Ÿ› Bug fixes + +- [Android] Fix animation resuming by casting image to GifDrawable. ([#37363](https://github.com/expo/expo/pull/37363) by [@Wenszel](https://github.com/Wenszel)) +- [Web] Fix `alt` as an alias for `accessibilityLabel` ([#37682](https://github.com/expo/expo/pull/37682) by [@huextrat](https://github.com/huextrat)) +- [iOS] Fix caching resized images from Photo Library. ([#38105](https://github.com/expo/expo/pull/38105) by [@jakex7](https://github.com/jakex7)) +- [iOS] Fix `generatePlaceholder` method syntax error by removing unwanted trailing comma. ([#38318](https://github.com/expo/expo/pull/38318) by [@bortolilucas](https://github.com/bortolilucas)) + +## 2.4.0 - 2025-07-17 + +### ๐ŸŽ‰ New features + +- [iOS] Add a new prop - `enforceEarlyResizing` to reduce the memory usage of the image view. ([#37909](https://github.com/expo/expo/pull/37909) by [@lukmccall](https://github.com/lukmccall)) + +### ๐Ÿ› Bug fixes + +- [iOS] Speed up displaying local assets. ([#37795](https://github.com/expo/expo/pull/37795) by [@aleqsio](https://github.com/aleqsio)) +- [iOS] Fix some operation were incorrectly cancelled. ([#37987](https://github.com/expo/expo/pull/37987) by [@lukmccall](https://github.com/lukmccall)) + +## 2.3.2 - 2025-07-01 + +### ๐Ÿ› Bug fixes + +- [iOS] Use specified cache type when no transformation is applied ([#37777](https://github.com/expo/expo/pull/37777) by [@jakex7](https://github.com/jakex7)) + +## 2.3.1 - 2025-07-01 + +### ๐Ÿ› Bug fixes + +- [iOS] Fixed contentPosition is not correct after switching theme. ([#37374](https://github.com/expo/expo/pull/37374) by [@kudo](https://github.com/kudo)) + +### ๐Ÿ“š 3rd party library updates + +- [Android] Bumped GIF Glide plugin to 3.0.5 for Android 16KB page size support. ([#37454](https://github.com/expo/expo/pull/37454) by [@kudo](https://github.com/kudo)) + +## 2.3.0 - 2025-06-11 + +### ๐Ÿ›  Breaking changes + +- [iOS] `useAppleWebpCodec` has been moved from the source object to the component's prop to make it usable with the local assets. ([#37300](https://github.com/expo/expo/pull/37300) by [@tsapeta](https://github.com/tsapeta)) + +### ๐Ÿ› Bug fixes + +- [iOS] Fix blurry images when using `tintColor` by scaling `imageThumbnailPixelSize` with screen density. ([#37235](https://github.com/expo/expo/pull/37235) by [@hirbod](https://github.com/hirbod)) + +## 2.2.1 - 2025-06-10 + +_This version does not introduce any user-facing changes._ + +## 2.2.0 - 2025-06-04 + +### ๐ŸŽ‰ New features + +- Add imperative api to lock/unlock/reload resource. ([#36912](https://github.com/expo/expo/pull/36912) by [@jakex7](https://github.com/jakex7)) + +### ๐Ÿ› Bug fixes + +- Fix React Server Components support. ([#36801](https://github.com/expo/expo/pull/36801) by [@EvanBacon](https://github.com/EvanBacon)) +- [iOS] Fix PhotoLibrary assets being scaled twice. ([#36776](https://github.com/expo/expo/pull/36776) by [@alanjhughes](https://github.com/alanjhughes)) +- [iOS] Don't add transformers when unnecessary. ([#36884](https://github.com/expo/expo/pull/36884) by [@jakex7](https://github.com/jakex7)) +- [Web] Fix `tintColor` in React 19. ([#37133](https://github.com/expo/expo/pull/37133) by [@bradleyayers](https://github.com/bradleyayers)) + +## 2.1.7 โ€” 2025-05-06 + +_This version does not introduce any user-facing changes._ + +## 2.1.6 โ€” 2025-04-30 + +_This version does not introduce any user-facing changes._ + +## 2.1.5 โ€” 2025-04-25 + +### ๐Ÿ› Bug fixes + +- Fixed `CUICatalog: Invalid asset name supplied: ''` error on iOS when the path is empty. ([#36294](https://github.com/expo/expo/pull/36294) by [@Innei](https://github.com/Innei)) + +## 2.1.4 โ€” 2025-04-14 + +### ๐Ÿ› Bug fixes + +- Fixed SVG image tinting on iOS. ([#35927](https://github.com/expo/expo/pull/35927) by [@kudo](https://github.com/kudo)) +- [Android] Fixed OutOfMemoryError crash when displaying some gif images ([#36097](https://github.com/expo/expo/pull/36097) by [@rahimrahman](https://github.com/rahimrahman)) + +## 2.1.3 โ€” 2025-04-11 + +_This version does not introduce any user-facing changes._ + +## 2.1.2 โ€” 2025-04-09 + +_This version does not introduce any user-facing changes._ + +## 2.1.1 โ€” 2025-04-08 + +### ๐Ÿ› Bug fixes + +- Fixed SVG image tinting on iOS. ([#35927](https://github.com/expo/expo/pull/35927) by [@kudo](https://github.com/kudo)) + +## 2.1.0 โ€” 2025-04-04 + +### ๐Ÿ›  Breaking changes + +- upgrade RN to 0.78 ([#35050](https://github.com/expo/expo/pull/35050) by [@vonovak](https://github.com/vonovak)) + +### ๐ŸŽ‰ New features + +- type-compatibility with react-native 0.77 ([#34027](https://github.com/expo/expo/pull/34027) by [@vonovak](https://github.com/vonovak)) +- Added `ImageSource.useAppleWebpCodec` on iOS to allow using libwebp codec for better animation performance on some images. ([#35802](https://github.com/expo/expo/pull/35802) by [@kudo](https://github.com/kudo)) + +### ๐Ÿ› Bug fixes + +- Rename `fetchpriority` prop to `fetchPriority` to silence web error. ([#35411](https://github.com/expo/expo/pull/35411) by [@EvanBacon](https://github.com/EvanBacon)) +- Fixed `tintColor="currentColor"` conflicts on web. ([#34604](https://github.com/expo/expo/pull/34604) by [@bradleyayers](https://github.com/bradleyayers)) + +### ๐Ÿ’ก Others + +- Update `ImageProps` type so `children` are omitted. ([#33210](https://github.com/expo/expo/pull/33210) by [@ashaller2017](https://github.com/ashaller2017)) +- [Android] Started using expo modules gradle plugin. ([#34176](https://github.com/expo/expo/pull/34176) by [@lukmccall](https://github.com/lukmccall)) +- [iOS] Fix warnings which will become errors in Swift 6. ([#35428](https://github.com/expo/expo/pull/35428) by [@behenate](https://github.com/behenate)) + +### ๐Ÿ“š 3rd party library updates + +- Bumped `SDWebImage` to 5.21.0. ([#35795](https://github.com/expo/expo/pull/35795) by [@kudo](https://github.com/kudo)) + +## 2.0.7 - 2025-03-26 + +### ๐Ÿ› Bug fixes + +- [iOS] Fixed image be cropped with `contentPosition` on New Architecture mode. ([#35630](https://github.com/expo/expo/pull/35630) by [@kudo](https://github.com/kudo)) + +## 2.0.6 - 2025-02-19 + +### ๐Ÿ› Bug fixes + +- [Android] Fixes a regression in `loadAsync` from [#34767](https://github.com/expo/expo/pull/34767). ([#34965](https://github.com/expo/expo/pull/34965) by [@alanjhughes](https://github.com/alanjhughes)) ([#34767](https://github.com/expo/expo/pull/34767), [#34965](https://github.com/expo/expo/pull/34965) by [@alanjhughes](https://github.com/alanjhughes)) + +## 2.0.5 - 2025-02-10 + +### ๐Ÿ› Bug fixes + +- [Android] Add headers to `loadAsync` requests. ([#34767](https://github.com/expo/expo/pull/34767) by [@alanjhughes](https://github.com/alanjhughes)) + +## 2.0.4 - 2025-01-10 + +_This version does not introduce any user-facing changes._ + +## 2.0.3 - 2024-11-29 + +### ๐Ÿ› Bug fixes + +- [Android] Fixed `useImage` causing a native crash when uri is unresolvable. ([#33268](https://github.com/expo/expo/pull/33268) by [@lukmccall](https://github.com/lukmccall)) +- [Android] Fixes adding a blurhash placeholder to `Image` or `ImageBackground` causing blurry version of the image. ([#33272](https://github.com/expo/expo/pull/33272) by [@lukmccall](https://github.com/lukmccall)) + +## 2.0.2 โ€” 2024-11-22 + +### ๐Ÿ’ก Others + +- Add a warning when `children` are passed to `Image`. ([#33139](https://github.com/expo/expo/pull/33139) by [@alanjhughes](https://github.com/alanjhughes)) + +## 2.0.1 โ€” 2024-11-19 + +### ๐Ÿ› Bug fixes + +- [Android] Fixes Gif animations never stopping even when loop count is set to 1. ([#32944](https://github.com/expo/expo/pull/32944) by [@lukmccall](https://github.com/lukmccall)) +- [Android] Fixed `borderColor` is not applied. ([#33026](https://github.com/expo/expo/pull/33026) by [@lukmccall](https://github.com/lukmccall)) +- [Android] Fixed `border` related props weren't applied correctly. ([#33078](https://github.com/expo/expo/pull/33078) by [@lukmccall](https://github.com/lukmccall)) + +### ๐Ÿ’ก Others + +- [Android] migrate ImageView to CSSBackgroundDrawable ([#33024](https://github.com/expo/expo/pull/33024) by [@vonovak](https://github.com/vonovak)) + +## 2.0.0 โ€” 2024-11-11 + +_This version does not introduce any user-facing changes._ + +## 2.0.0-preview.1 โ€” 2024-10-31 + +### ๐Ÿ› Bug fixes + +- Fixed displaying a placeholder when `useImage` hook is used. ([#32430](https://github.com/expo/expo/pull/32430) by [@tsapeta](https://github.com/tsapeta)) + +## 2.0.0-preview.0 โ€” 2024-10-22 + +### ๐Ÿ›  Breaking changes + +- Bumped iOS and tvOS deployment target to 15.1. ([#30840](https://github.com/expo/expo/pull/30840), [#30871](https://github.com/expo/expo/pull/30871) by [@tsapeta](https://github.com/tsapeta)) + +### ๐ŸŽ‰ New features + +- Added `Image.loadAsync` API. ([#25079](https://github.com/expo/expo/pull/25079) by [@tsapeta](https://github.com/tsapeta), [#26824](https://github.com/expo/expo/pull/26824) by [@aleqsio](https://github.com/aleqsio), [#31575](https://github.com/expo/expo/pull/31575) by [@tsapeta](https://github.com/tsapeta)) +- Add basic React Server Component support. ([#29869](https://github.com/expo/expo/pull/29869) by [@EvanBacon](https://github.com/EvanBacon)) +- [iOS] Added support for rendering shared image refs. ([#30661](https://github.com/expo/expo/pull/30661) by [@tsapeta](https://github.com/tsapeta)) +- [Android] Added support for rendering shared image refs. ([#31098](https://github.com/expo/expo/pull/31098) by [@lukmccall](https://github.com/lukmccall)) +- [Android] Added support for rendering shared refs of `Bitmap's`. ([#31440](https://github.com/expo/expo/pull/31440) by [@lukmccall](https://github.com/lukmccall)) +- Added `useImage` hook. ([#31171](https://github.com/expo/expo/pull/31171) by [@tsapeta](https://github.com/tsapeta)) +- Added downscaling options to `useImage` hook. ([#32113](https://github.com/expo/expo/pull/32113) by [@lukmccall](https://github.com/lukmccall)) + +### ๐Ÿ› Bug fixes + +- Fix props not being passed to parent container. ([#29416](https://github.com/expo/expo/pull/29416) by [@aleqsio](https://github.com/aleqsio)) +- Add missing `react` and `react-native` peer dependencies for isolated modules. ([#30469](https://github.com/expo/expo/pull/30469) by [@byCedric](https://github.com/byCedric)) +- Add missing `react-native-web` optional peer dependency for isolated modules. ([#30689](https://github.com/expo/expo/pull/30689) by [@byCedric](https://github.com/byCedric)) +- [Web] Fix type incompatibility between style prop and `@types/react-native-web` ([#31150](https://github.com/expo/expo/pull/31150) by [@adamhari](https://github.com/adamhari)) +- [iOS] Fixed `isAnimated` property always returning `true`. ([#31834](https://github.com/expo/expo/pull/31834) by [@tsapeta](https://github.com/tsapeta)) +- [iOS] Fixed `stopAnimating` broken on downscaled animated images. ([#32053](https://github.com/expo/expo/pull/32053) by [@kudo](https://github.com/kudo)) + +### ๐Ÿ’ก Others + +- Use the `src` folder as the Metro target. ([#30665](https://github.com/expo/expo/pull/30665) by [@tsapeta](https://github.com/tsapeta)) +- Provide image's memory footprint for better garbage collection. ([#31168](https://github.com/expo/expo/pull/31168) by [@tsapeta](https://github.com/tsapeta) & [#31784](https://github.com/expo/expo/pull/31784) by [@lukmccall](https://github.com/lukmccall)) + +### โš ๏ธ Notices + +- Added support for React Native 0.75.x. ([#30034](https://github.com/expo/expo/pull/30034) by [@gabrieldonadel](https://github.com/gabrieldonadel)) +- Added support for React Native 0.76.x. ([#31552](https://github.com/expo/expo/pull/31552) by [@gabrieldonadel](https://github.com/gabrieldonadel)) + +## 1.13.0 - 2024-09-23 + +### ๐ŸŽ‰ New features + +- Added `onDisplay` event. ([#31581](https://github.com/expo/expo/pull/31581) by [@tsapeta](https://github.com/tsapeta)) + +## 1.12.15 - 2024-08-24 + +_This version does not introduce any user-facing changes._ + +## 1.12.13 - 2024-07-16 + +### ๐Ÿ› Bug fixes + +- [Web] Fix blurhash not working and causing fetches to `blurhash:/` uri. ([#27587](https://github.com/expo/expo/pull/27587) by [@aleqsio](https://github.com/aleqsio)) +- [iOS] Fixed `blurRadius` not working. Also made the effect render more consistently across platforms. ([#29678](https://github.com/expo/expo/pull/29678) by [@vonovak](https://github.com/vonovak)) +- Fixed `tintColor` not working on Safari browsers. ([#29169](https://github.com/expo/expo/pull/29169) by [@bradleyayers](https://github.com/bradleyayers)) +- Fixed reanimated support on web. ([#29197](https://github.com/expo/expo/pull/29197) by [@nishan](https://github.com/intergalacticspacehighway)) ([#29197](https://github.com/expo/expo/pull/29197) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway)) + +## 1.12.12 - 2024-06-13 + +### ๐Ÿ’ก Others + +- Removed @react-native/assets-registry dependency. ([#29541](https://github.com/expo/expo/pull/29541) by [@kudo](https://github.com/kudo)) + +## 1.12.11 - 2024-06-06 + +_This version does not introduce any user-facing changes._ + +## 1.12.10 - 2024-06-05 + +### ๐Ÿ’ก Others + +- Pin @react-native subpackage versions to 0.74.83. ([#29441](https://github.com/expo/expo/pull/29441) by [@kudo](https://github.com/kudo)) + +## 1.12.9 โ€” 2024-05-09 + +### ๐Ÿ’ก Others + +- Added setup for native unit tests. ([#28678](https://github.com/expo/expo/pull/28678) by [@tsapeta](https://github.com/tsapeta)) + +## 1.12.8 โ€” 2024-05-06 + +_This version does not introduce any user-facing changes._ + +## 1.12.7 โ€” 2024-05-04 + +### ๐ŸŽ‰ New features + +- Added support for displaying animated AVIF images on Android. ([#28609](https://github.com/expo/expo/pull/28609) by [@fobos531](https://github.com/fobos531)) + +### ๐Ÿ› Bug fixes + +- Fix `avif` images not rendering. ([#28608](https://github.com/expo/expo/pull/28608) by [@alanjhughes](https://github.com/alanjhughes)) + +## 1.12.6 โ€” 2024-05-02 + +_This version does not introduce any user-facing changes._ + +## 1.12.5 โ€” 2024-05-01 + +_This version does not introduce any user-facing changes._ + +## 1.12.4 โ€” 2024-04-24 + +### ๐Ÿ› Bug fixes + +- Fixed an issue where certain images would not animate at certain sizes on `iOS`. ([#28335](https://github.com/expo/expo/pull/28335) by [@fobos531](https://github.com/fobos531)) +- Fixed SVG assets without viewbox attribute not being rendered on Android. ([#28369](https://github.com/expo/expo/pull/28369) by [@lukmccall](https://github.com/lukmccall)) + +## 1.12.3 โ€” 2024-04-23 + +_This version does not introduce any user-facing changes._ + +## 1.12.2 โ€” 2024-04-22 + +### ๐Ÿ› Bug fixes + +- Fixed an issue where certain images would not animate at certain sizes on `iOS`. ([#28335](https://github.com/expo/expo/pull/28335) by [@fobos531](https://github.com/fobos531), [#28371](https://github.com/expo/expo/pull/28371) by [@tsapeta](https://github.com/tsapeta)) + +## 1.12.1 โ€” 2024-04-19 + +_This version does not introduce any user-facing changes._ + +## 1.12.0 โ€” 2024-04-18 + +### ๐ŸŽ‰ New features + +- On `iOS`, support loading assets in the native project. This already worked on android. ([#27251](https://github.com/expo/expo/pull/27251) by [@alanjhughes](https://github.com/alanjhughes)) +- Support prefetching images with HTTP headers. ([#28133](https://github.com/expo/expo/pull/28133) by [@toy0605](https://github.com/toy0605)) + +### ๐Ÿ› Bug fixes + +- [iOS] Fixed an issue where data URIs caused crashes on iOS16+. ([#28320](https://github.com/expo/expo/pull/28320) by [@aleqsio](https://github.com/aleqsio)) +- Refactor web implementations of `useSourceSelection` to avoid unnecessary rerenders. ([#27569](https://github.com/expo/expo/pull/27569) by [@aleqsio](https://github.com/aleqsio)) +- Fixed an issue where `isBlurhashString` always returned `true` .([#27251](https://github.com/expo/expo/pull/27251) by [@alanjhughes](https://github.com/alanjhughes)) +- Fix #available check to account for tvOS. ([#27272](https://github.com/expo/expo/pull/27272) by [@alanjhughes](https://github.com/alanjhughes)) +- Fixed jest may cause `RangeError: Invalid string length` error when generating a snapshot. ([#27354](https://github.com/expo/expo/pull/27354) by [@lukmccall](https://github.com/lukmccall)) +- Fixed placeholders weren't correctly scaled down on the Android. ([#28255](https://github.com/expo/expo/pull/28255) by [@lukmccall](https://github.com/lukmccall)) + +### ๐Ÿ’ก Others + +- [iOS] Bump SDWebImage to `5.19.1` to include the 3rd-party privacy manifest. ([#27874](https://github.com/expo/expo/pull/27874) by [@aparedes](https://github.com/aparedes), []() by [@tsapeta](https://github.com/tsapeta)) +- Use `typeof window` checks for removing server code. ([#27514](https://github.com/expo/expo/pull/27514) by [@EvanBacon](https://github.com/EvanBacon)) +- Removed deprecated backward compatible Gradle settings. ([#28083](https://github.com/expo/expo/pull/28083) by [@kudo](https://github.com/kudo)) +- [iOS] Bump SDWebImageWebPCoder to `0.14.6`. ([#28273](https://github.com/expo/expo/pull/28273) by [@alanjhughes](https://github.com/alanjhughes)) +- Automatically clean blurhash cache when the app's memory is running low. ([#28276](https://github.com/expo/expo/pull/28276) by [@lukmccall](https://github.com/lukmccall)) + +## 1.11.0 โ€” 2024-02-05 + +### ๐ŸŽ‰ New features + +- [Android] Adds new prop `decodeFormat` to specify the format that should be used during the decoding process. ([#26442](https://github.com/expo/expo/pull/26442) by [@lukmccall](https://github.com/lukmccall)) +- [iOS] Added `generateBlurhashAsync` function. ([#26430](https://github.com/expo/expo/pull/26430) by [@aleqsio](https://github.com/aleqsio)) +- [Android] Adds automatic downsampling when the asset exceeds the hardware bitmap size limit. ([#26792](https://github.com/expo/expo/pull/26792) by [@lukmccall](https://github.com/lukmccall)) + +### ๐Ÿ› Bug fixes + +- Fixed ResizeObserver attaching on every image transition. ([#25819](https://github.com/expo/expo/pull/25819) by [@aleqsio](https://github.com/aleqsio)) +- [Android] Fixed the tine color was applied to the mask element. ([#26323](https://github.com/expo/expo/pull/26323) by [@lukmccall](https://github.com/lukmccall)) +- [Web] Fixed `nativeViewRef` invalid prop warning. ([#25922](https://github.com/expo/expo/pull/25922) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway)) + +## 1.10.4 - 2024-01-18 + +_This version does not introduce any user-facing changes._ + +## 1.10.3 - 2024-01-12 + +_This version does not introduce any user-facing changes._ + +## 1.10.2 - 2024-01-10 + +### ๐Ÿ› Bug fixes + +- [Android] Fixed the issue with the application of tint color when an element does not have a style assigned to it. ([#26251](https://github.com/expo/expo/pull/26251) by [@lukmccall](https://github.com/lukmccall)) +- [Android] Fixed the tint color wasn't applied to the root element. ([#26339](https://github.com/expo/expo/pull/26339) by [@lukmccall](https://github.com/lukmccall)) +- [Android] Fixed that the tint color on SVGs can't be changed dynamically. ([#26350](https://github.com/expo/expo/pull/26350) by [@lukmccall](https://github.com/lukmccall)) + +### ๐Ÿ’ก Others + +- Replace deprecated `com.facebook.react:react-native:+` Android dependency with `com.facebook.react:react-android`. ([#26237](https://github.com/expo/expo/pull/26237) by [@kudo](https://github.com/kudo)) + +## 1.10.1 - 2023-12-19 + +_This version does not introduce any user-facing changes._ + +## 1.10.0 โ€” 2023-12-12 + +### ๐ŸŽ‰ New features + +- Added support for React Native 0.73.0. ([#24971](https://github.com/expo/expo/pull/24971), [#25453](https://github.com/expo/expo/pull/25453) by [@gabrieldonadel](https://github.com/gabrieldonadel)) + +## 1.9.0 โ€” 2023-11-14 + +### ๐Ÿ›  Breaking changes + +- Bumped iOS deployment target to 13.4. ([#25063](https://github.com/expo/expo/pull/25063) by [@gabrieldonadel](https://github.com/gabrieldonadel)) +- On `Android` bump `compileSdkVersion` and `targetSdkVersion` to `34`. ([#24708](https://github.com/expo/expo/pull/24708) by [@alanjhughes](https://github.com/alanjhughes)) + +### ๐ŸŽ‰ New features + +- [Android] The stability of the memory cache key generation has been improved. ([#25372](https://github.com/expo/expo/pull/25372) by [@lukmccall](https://github.com/lukmccall)) + +### ๐Ÿ’ก Others + +- Collapse re-export of `react-native/Libraries/Image/AssetRegistry` to `@react-native/assets-registry/registry`. ([#25265](https://github.com/expo/expo/pull/25265) by [@EvanBacon](https://github.com/EvanBacon)) +- [Android] Changed how the `tintColor` is applied to the SVG. ([#25377](https://github.com/expo/expo/pull/25377) by [@lukmccall](https://github.com/lukmccall)) + +## 1.8.0 โ€” 2023-11-13 + +### ๐ŸŽ‰ New features + +- Return a promise in the `prefetch` method. ([#25196](https://github.com/expo/expo/pull/25196) by [@gkasdorf](https://github.com/gkasdorf)) +- [Android] Added `autoplay` prop and `startAnimating()` and `stopAnimating()` functions to reflect changes made to iOS in [#25008](https://github.com/expo/expo/pull/25008). ([#25124](https://github.com/expo/expo/pull/25124) by [@gkasdorf](https://github.com/gkasdorf)) + +### ๐Ÿ› Bug fixes + +- [Android] Fix `contentFit` not working for `SVG` images. ([#25187](https://github.com/expo/expo/pull/25187) by [@behenate](https://github.com/behenate)) +- [iOS] Start loading the image before the view mounts to fix issues with the PagerView. ([#25343](https://github.com/expo/expo/pull/25343) by [@tsapeta](https://github.com/tsapeta)) +- [Android] Fix `SVG` not scaling correctly in the release mode. ([#25326](https://github.com/expo/expo/pull/25326) by [@lukmccall](https://github.com/lukmccall)) +- [Android] Fix incorrect `intrinsicSize` returned for SVGs. ([#25048](https://github.com/expo/expo/pull/25048) by [@behenate](https://github.com/behenate)) + +### ๐Ÿ’ก Others + +- [Android] Add tracing. ([#25251](https://github.com/expo/expo/pull/25251) by [@lukmccall](https://github.com/lukmccall)) + +## 1.7.0 โ€” 2023-11-01 + +### ๐ŸŽ‰ New features + +- [iOS] Added support for `allowDownscaling` prop. ([#25012](https://github.com/expo/expo/pull/25012) by [@behenate](https://github.com/behenate)) +- Added `getCachePathAsync()` to retrieve the path of the cached image file if it exists ([#24980](https://github.com/expo/expo/pull/24980) by [@gkasdorf](https://github.com/gkasdorf)) +- [iOS] Added `autoplay` prop to control whether an animated image will automatically animate or not ([#25008](https://github.com/expo/expo/pull/25008) by [@gkasdorf](https://github.com/gkasdorf)) +- [iOS] Added `startAnimating()` and `stopAnimating()` functions to start or stop an image's animation ([#25008](https://github.com/expo/expo/pull/25008) by [@gkasdorf](https://github.com/gkasdorf)) + +### ๐Ÿ› Bug fixes + +- [Android] fix crash when loading local image files with no file extension ([#24201](https://github.com/expo/expo/pull/25032) by [@kadikraman](https://github.com/kadikraman)) +- [iOS] Fix compilation on tvOS. ([#25010](https://github.com/expo/expo/pull/25010) by [@douglowder](https://github.com/douglowder)) +- [iOS] Fixed issue where some animated images would cause the app to hang ([#25008](https://github.com/expo/expo/pull/25008) by [@gkasdorf](https://github.com/gkasdorf)) + +## 1.6.1 โ€” 2023-11-01 + +### ๐Ÿ› Bug fixes + +- [Android] fix crash when loading local image files with no file extension ([#24201](https://github.com/expo/expo/pull/25032) by [@kadikraman](https://github.com/kadikraman)) + +## 1.3.5 โ€” 2023-11-01 + +### ๐Ÿ› Bug fixes + +- [Android] fix crash when loading local image files with no file extension ([#24201](https://github.com/expo/expo/pull/25032) by [@kadikraman](https://github.com/kadikraman)) + +## 1.6.0 โ€” 2023-10-17 + +### ๐Ÿ›  Breaking changes + +- Dropped support for Android SDK 21 and 22. ([#24201](https://github.com/expo/expo/pull/24201) by [@behenate](https://github.com/behenate)) + +### ๐ŸŽ‰ New features + +- Added support for the `headers` key in the `source` object on web. ([#24447](https://github.com/expo/expo/pull/24447) by [@aleqsio](https://github.com/aleqsio)) +- Add support for setting `tintColor` on SVGs on Android (part 1) ([#24733](https://github.com/expo/expo/pull/24733) by [@alanjhughes](https://github.com/alanjhughes) and [@kadikraman](https://github.com/kadikraman)) +- Add support for setting `tintColor` on SVGs on Android (part 2) ([#24888](https://github.com/expo/expo/pull/24888) by [@kadikraman](https://github.com/kadikraman)) + +### ๐Ÿ› Bug fixes + +- Remove `GlideWebpDecoder` until they update their `libwebp` dependency. ([#24656](https://github.com/expo/expo/pull/24656) by [@alanjhughes](https://github.com/alanjhughes)) +- [web] Fix content fit not being applied correctly when using hash placeholders. ([#24542](https://github.com/expo/expo/pull/24542) by [@aleqsio](https://github.com/aleqsio)) +- [macCatalyst] Fix build with `ImageAnalyzer` on macCatalyst below 17.0. ([#24880](https://github.com/expo/expo/pull/24880) by [@kesha-antonov](https://github.com/kesha-antonov)) + +### ๐Ÿ’ก Others + +- Ship untranspiled JSX to support custom handling of `jsx` and `createElement`. ([#24889](https://github.com/expo/expo/pull/24889) by [@EvanBacon](https://github.com/EvanBacon)) +- Make `placeholderContentFit` visible in the docs. ([#24801](https://github.com/expo/expo/pull/24801) by [@behenate](https://github.com/behenate)) + +## 1.0.2 โ€” 2023-09-29 + +### ๐Ÿ› Bug fixes + +- Remove `GlideWebpDecoder` until they update their `libwebp` dependency. ([#24656](https://github.com/expo/expo/pull/24656) by [@alanjhughes](https://github.com/alanjhughes)) + +## 1.3.4 โ€” 2023-09-28 + +### ๐Ÿ› Bug fixes + +- Remove `GlideWebpDecoder` until they update their `libwebp` dependency. ([#24656](https://github.com/expo/expo/pull/24656) by [@alanjhughes](https://github.com/alanjhughes)) + +## 1.5.2 โ€” 2023-09-18 + +_This version does not introduce any user-facing changes._ + +## 1.3.3 โ€” 2023-09-15 + +### ๐Ÿ› Bug fixes + +- Fixed placeholders aren't always replaced by full-size images on Android. ([#23705](https://github.com/expo/expo/pull/23705) by [@lukmccall](https://github.com/lukmccall)) +- Fix the image components rendering incorrect assets when the Proguard is enabled on Android. ([#23704](https://github.com/expo/expo/pull/23704) by [@lukmccall](https://github.com/lukmccall)) +- Fixed gif and awebp memory leak on Android. ([#24259](https://github.com/expo/expo/pull/24259) by [@jingpeng](https://github.com/jingpeng)) +- Suppress "Operation cancelled by user during sending the request" error when the load request is canceled (interrupted) by a new one. ([#24279](https://github.com/expo/expo/pull/24279) by [@tsapeta](https://github.com/tsapeta)) + +## 1.5.1 โ€” 2023-09-11 + +### ๐Ÿ› Bug fixes + +- Suppress "Operation cancelled by user during sending the request" error when the load request is canceled (interrupted) by a new one. ([#24279](https://github.com/expo/expo/pull/24279) by [@tsapeta](https://github.com/tsapeta)) +- Fixed gif and awebp memory leak on Android. ([#24259](https://github.com/expo/expo/pull/24259) by [@jingpeng](https://github.com/jingpeng)) + +## 1.5.0 โ€” 2023-09-04 + +### ๐ŸŽ‰ New features + +- Added support for React Native 0.73. ([#24018](https://github.com/expo/expo/pull/24018) by [@kudo](https://github.com/kudo)) + +### ๐Ÿ’ก Others + +- On iOS, bump SDWebImage versions. ([#23858](https://github.com/expo/expo/pull/23858) by [@alanjhughes](https://github.com/alanjhughes)) + +## 1.4.1 โ€” 2023-08-02 + +_This version does not introduce any user-facing changes._ + +## 1.4.0 โ€” 2023-07-28 + +### ๐ŸŽ‰ New features + +- [Web] Add support for `tintColor` prop on web. ([#23434](https://github.com/expo/expo/pull/23434) by [@aleqsio](https://github.com/aleqsio)) +- [Web] Add support for static image responsiveness using `srcset` attributes. ([#22088](https://github.com/expo/expo/pull/22088) by [@aleqsio](https://github.com/aleqsio)) + +### ๐Ÿ› Bug fixes + +- Fixed placeholders aren't always replaced by full-size images on Android. ([#23705](https://github.com/expo/expo/pull/23705) by [@lukmccall](https://github.com/lukmccall)) +- Fixed the image components rendering incorrect assets when the Proguard is enabled on Android. ([#23704](https://github.com/expo/expo/pull/23704) by [@lukmccall](https://github.com/lukmccall)) + +### ๐Ÿ’ก Others + +- [Web] Refactored and split some functions for better readability. ([#23465](https://github.com/expo/expo/pull/23465) by [@aleqsio](https://github.com/aleqsio)) + +## 1.3.2 - 2023-07-12 + +### ๐Ÿ› Bug fixes + +- [iOS] Fixed `tintColor` prop not working for SVGs. ([#23418](https://github.com/expo/expo/pull/23418) by [@tsapeta](https://github.com/tsapeta)) + +## 1.3.1 - 2023-06-29 + +### ๐Ÿ› Bug fixes + +- Fixed an issue where recyclingKey would reset the image source on mount. ([#23187](https://github.com/expo/expo/pull/23187) by [@hirbod](https://github.com/hirbod)) + +## 1.3.0 โ€” 2023-06-13 + +### ๐ŸŽ‰ New features + +- Add prefetch implementation on web. ([#22630](https://github.com/expo/expo/pull/22630) by [@aleqsio](https://github.com/aleqsio)) +- Add `ImageBackground` component. ([#22347](https://github.com/expo/expo/pull/22347) by [@alanjhughes](https://github.com/alanjhughes)) +- Added support for React Native 0.72. ([#22588](https://github.com/expo/expo/pull/22588) by [@kudo](https://github.com/kudo)) + +### ๐Ÿ› Bug fixes + +- Fixed styles order breaking layouting on web. ([#22630](https://github.com/expo/expo/pull/22630) by [@aleqsio](https://github.com/aleqsio)) +- Uses prop spreading on web to pass all unused props to the native image component ([#22340](https://github.com/expo/expo/pull/22340) by [@makkarMeenu](https://github.com/makkarMeenu)) +- Fixed Android build warnings for Gradle version 8. ([#22537](https://github.com/expo/expo/pull/22537), [#22609](https://github.com/expo/expo/pull/22609) by [@kudo](https://github.com/kudo)) + +### ๐Ÿ’ก Others + +- Updated `SDWebImage` to `5.15.8`, `SDWebImageWebPCoder` to `0.11.0` and `SDWebImageSVGCoder` to `1.7.0`. ([#22576](https://github.com/expo/expo/pull/22576) by [@tsapeta](https://github.com/tsapeta)) + +## 1.2.3 โ€” 2023-05-16 + +### ๐Ÿ› Bug fixes + +- Upgrade SDWebImageAVIFCoder to fix compiling issue with libavif < 0.11.0. ([#22491](https://github.com/expo/expo/pull/22491) by [@matinzd](https://github.com/matinzd)) + +## 1.2.2 โ€” 2023-04-27 + +### ๐Ÿ› Bug fixes + +- Fix for the "limited" media library permission. ([#22261](https://github.com/expo/expo/pull/22261) by [@tsapeta](https://github.com/tsapeta)) + +## 1.2.1 โ€” 2023-04-17 + +### ๐Ÿ› Bug fixes + +- [Android] Fix `url` property returned by the `onLoad` event. ([#22161](https://github.com/expo/expo/pull/22161) by [@lukmccall](https://github.com/lukmccall)) +- [Android] Fix images not loading after the app was foregrounded. ([#22159](https://github.com/expo/expo/pull/22159) by [@lukmccall](https://github.com/lukmccall)) +- [Android] Fixed image was loaded event if the view dimensions were 0. ([#22157](https://github.com/expo/expo/pull/22157) by [@lukmccall](https://github.com/lukmccall)) +- Fix generating the image from ThumbHash that starts with a slash character. ([#22160](https://github.com/expo/expo/pull/22160) by [@tsapeta](https://github.com/tsapeta)) + +## 1.2.0 โ€” 2023-04-14 + +### ๐ŸŽ‰ New features + +- [Web] Add support for `require()` assets. ([#21798](https://github.com/expo/expo/pull/21798) by [@aleqsio](https://github.com/aleqsio)) +- Add `alt` prop as an alias to `accessibilityLabel`. ([#21884](https://github.com/expo/expo/pull/21884) by [@EvanBacon](https://github.com/EvanBacon)) +- [Web] Add `accessibilityLabel` support on web. ([#21884](https://github.com/expo/expo/pull/21884) by [@EvanBacon](https://github.com/EvanBacon)) +- Added `ThumbHash` support for Android, iOS and Web. ([#21952](https://github.com/expo/expo/pull/21952) by [@behenate](https://github.com/behenate)) + +### ๐Ÿ› Bug fixes + +- [Web] Improve transition behavior when switching back and forth two sources. ([#22099](https://github.com/expo/expo/pull/22099) by [@aleqsio](https://github.com/aleqsio)) +- [Web] Prevent breaking in static rendering environments. ([#21883](https://github.com/expo/expo/pull/21883) by [@EvanBacon](https://github.com/EvanBacon)) +- [Android] Fixed image disappearing before navigation animation is complete. ([#22066](https://github.com/expo/expo/pull/22066) by [@sallen450](https://github.com/sallen450)) +- [Web] Fixed monorepo asset resolution in production for Metro web. ([#22094](https://github.com/expo/expo/pull/22094) by [@EvanBacon](https://github.com/EvanBacon)) + +## 1.1.0 โ€” 2023-03-25 + +### ๐ŸŽ‰ New features + +- [Android] Add automatic asset downscaling to improve performance. ([#21628](https://github.com/expo/expo/pull/21628) by [@lukmccall](https://github.com/lukmccall)) + +### ๐Ÿ› Bug fixes + +- Fixed the `tintColor` not being passed to native view. ([#21576](https://github.com/expo/expo/pull/21576) by [@andrew-levy](https://github.com/andrew-levy)) +- Fixed `canvas: trying to use a recycled bitmap` on Android. ([#21658](https://github.com/expo/expo/pull/21658) by [@lukmccall](https://github.com/lukmccall)) +- Fixed crashes caused by empty placeholder or source on Android. ([#21695](https://github.com/expo/expo/pull/21695) by [@lukmccall](https://github.com/lukmccall)) +- Fixes `shouldDownscale` don't respect the scale factor on iOS. ([#21839](https://github.com/expo/expo/pull/21839) by [@ouabing](https://github.com/ouabing)) +- Fixes cache policy not being correctly applied when set to `none` on iOS. ([#21840](https://github.com/expo/expo/pull/21840) by [@ouabing](https://github.com/ouabing)) + +## 1.0.0 โ€” 2023-02-21 + +_This version does not introduce any user-facing changes._ + +## 1.0.0-rc.2 โ€” 2023-02-20 + +### ๐ŸŽ‰ New features + +- Added `recyclingKey` prop that allows reseting the image view content when the view is recycled. ([#21297](https://github.com/expo/expo/pull/21297) & [#21309](https://github.com/expo/expo/pull/21309) by [@tsapeta](https://github.com/tsapeta) & [@lukmccall](https://github.com/lukmccall)) + +## 1.0.0-rc.1 โ€” 2023-02-14 + +### ๐Ÿ› Bug fixes + +- Fixed `You can't start or clear loads in RequestListener or Target callbacks` on Android. ([#21192](https://github.com/expo/expo/pull/21192) by [@lukmccall](https://github.com/lukmccall)) +- Fixed SVGs are not rendered in the release mode on Android. ([#21214](https://github.com/expo/expo/pull/21214) by [@lukmccall](https://github.com/lukmccall)) +- Stop sending `onProgress` event when the asset size is unknown which led to diving by zero and a crash. ([#21215](https://github.com/expo/expo/pull/21215) by [@tsapeta](https://github.com/tsapeta)) + +## 1.0.0-rc.0 โ€” 2023-02-09 + +### ๐ŸŽ‰ New features + +- Added `placeholderContentFit` prop implementation on the web. ([#21106](https://github.com/expo/expo/pull/21106) by [@aleqsio](https://github.com/aleqsio)) + +## 1.0.0-beta.6 โ€” 2023-02-06 + +### ๐ŸŽ‰ New features + +- Added new prop `placeholderContentFit` to specify custom content fit on the placeholder. ([#21096](https://github.com/expo/expo/pull/21096) by [@magrinj](https://github.com/magrinj)) + +### ๐Ÿ› Bug fixes + +- [iOS] Fixed possible freezes by processing images concurrently off the main thread. ([#21086](https://github.com/expo/expo/pull/21086) by [@tsapeta](https://github.com/tsapeta)) + +## 1.0.0-beta.5 โ€” 2023-02-03 + +_This version does not introduce any user-facing changes._ + +## 1.0.0-beta.4 โ€” 2023-01-31 + +### ๐Ÿ› Bug fixes + +- Fixed a crash on Android where `isScreenReaderFocusable` crashes devices below api 28. ([#21012](https://github.com/expo/expo/pull/21012) by [@alanhughes](https://github.com/alanjhughes)) + +## 1.0.0-beta.3 โ€” 2023-01-30 + +### ๐Ÿ› Bug fixes + +- Fixed a crash on iOS below 16.0 introduced by the Live Text interaction feature. ([#20987](https://github.com/expo/expo/pull/20987) by [@tsapeta](https://github.com/tsapeta)) + +## 1.0.0-beta.2 โ€” 2023-01-25 + +### ๐ŸŽ‰ New features + +- Added support for Live text interaction. ([#20915](https://github.com/expo/expo/pull/20915) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway)) + +### ๐Ÿ› Bug fixes + +- `ImageProps` now extends `ViewProps`. ([#20942](https://github.com/expo/expo/pull/20942) by [@appden](https://github.com/appden)) + +## 1.0.0-beta.1 โ€” 2023-01-20 + +### ๐Ÿ› Bug fixes + +- Use `SDImageAWebPCoder` on iOS 14+ to speed up loading WebP images. ([#20897](https://github.com/expo/expo/pull/20897) by [@tsapeta](https://github.com/tsapeta)) + +### ๐Ÿ’ก Others + +- On Android bump `compileSdkVersion` and `targetSdkVersion` to `33`. ([#20721](https://github.com/expo/expo/pull/20721) by [@lukmccall](https://github.com/lukmccall)) +- Upgraded `SDWebImage` to `5.15.0` and `SDWebImageAVIFCoder` to `0.9.4`. ([#20898](https://github.com/expo/expo/pull/20898) by [@tsapeta](https://github.com/tsapeta)) + +## 1.0.0-beta.0 โ€” 2023-01-19 + +### ๐ŸŽ‰ New features + +- Added support for crossfade transition on Android. ([#20784](https://github.com/expo/expo/pull/20784) by [@lukmccall](https://github.com/lukmccall)) +- Added web support for the `blurRadius` prop. ([#20845](https://github.com/expo/expo/pull/20845) by [@aleqsio](https://github.com/aleqsio)) +- Support for `accessible` and `accessibilityLabel` props on Android. ([#20801](https://github.com/expo/expo/pull/20801) by [@lukmccall](https://github.com/lukmccall)) +- Support for `accessible` and `accessibilityLabel` props on iOS. ([#20892](https://github.com/expo/expo/pull/20892) by [@alanhughes](https://github.com/alanjhughes)) + +## 1.0.0-alpha.6 โ€” 2023-01-10 + +### ๐ŸŽ‰ New features + +- Introduced the `source.cacheKey` parameter to customize the key used for caching the source image. ([#20772](https://github.com/expo/expo/pull/20772) by [@tsapeta](https://github.com/tsapeta), [#20776](https://github.com/expo/expo/pull/20776) by [@lukmccall](https://github.com/lukmccall)) + +## 1.0.0-alpha.5 โ€” 2023-01-04 + +### ๐ŸŽ‰ New features + +- Added support for assets from the iOS Photo Library (`ph://` urls). ([#20700](https://github.com/expo/expo/pull/20700) by [@tsapeta](https://github.com/tsapeta)) + +### ๐Ÿ› Bug fixes + +- Fixed `ImageProps` type not allowing an array of styles. ([#20701](https://github.com/expo/expo/pull/20701) by [@tsapeta](https://github.com/tsapeta)) + +## 1.0.0-alpha.4 โ€” 2022-12-30 + +### ๐Ÿ› Bug fixes + +- Fixed compatibility with `react-native-shared-element` on iOS. ([#20592](https://github.com/expo/expo/pull/20592) by [@IjzerenHein](https://github.com/ijzerenhein)) + +## 1.0.0-alpha.3 โ€” 2022-12-21 + +### ๐ŸŽ‰ New features + +- Initial release ๐Ÿฅณ diff --git a/node_modules/expo-image/README.md b/node_modules/expo-image/README.md new file mode 100644 index 00000000..2f318420 --- /dev/null +++ b/node_modules/expo-image/README.md @@ -0,0 +1,64 @@ +

+ + expo-image + +

+ +A cross-platform, performant image component for React Native and Expo. + +## Main features + +- Designed for speed +- Support for many image formats (including animated ones) +- Disk and memory caching +- Supports [BlurHash](https://blurha.sh) and [ThumbHash](https://evanw.github.io/thumbhash/) - compact representations of a placeholder for an image +- Transitioning between images when the source changes (no more flickering!) +- Implements the CSS [`object-fit`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) and [`object-position`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) properties (see [`contentFit`](#contentfit) and [`contentPosition`](#contentposition) props) +- Uses performant [`SDWebImage`](https://github.com/SDWebImage/SDWebImage) and [`Glide`](https://github.com/bumptech/glide) under the hood + +## Supported image formats + +| Format | Android | iOS | Web | +| :--------: | :-----: | :-: | :--------------------------------------------: | +| WebP | โœ… | โœ… | โœ… | +| PNG / APNG | โœ… | โœ… | โœ… | +| AVIF | โœ… | โœ… | โœ… | +| HEIC | โœ… | โœ… | โŒ [not adopted yet](https://caniuse.com/heif) | +| JPEG | โœ… | โœ… | โœ… | +| GIF | โœ… | โœ… | โœ… | +| SVG | โœ… | โœ… | โœ… | +| ICO | โœ… | โœ… | โœ… | +| ICNS | โŒ | โœ… | โŒ | + +# API documentation + +- [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/image/) + +# Installation in managed Expo projects + +For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/image/). + +# Installation in bare React Native projects + +For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing. + +### Add the package to your npm dependencies + +``` +npx expo install expo-image +``` + +### Configure for iOS + +Run `npx pod-install` after installing the npm package. + +### Configure for Android + +No additional setup necessary. + +# Contributing + +Contributions are very welcome! Please refer to guidelines described in the [contributing guide](https://github.com/expo/expo#contributing). diff --git a/node_modules/expo-image/android/build.gradle b/node_modules/expo-image/android/build.gradle new file mode 100644 index 00000000..96ec610b --- /dev/null +++ b/node_modules/expo-image/android/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'com.android.library' + id 'kotlin-kapt' + id 'expo-module-gradle-plugin' +} + +android { + namespace "expo.modules.image" + defaultConfig { + versionCode 1 + versionName "3.0.11" + consumerProguardFiles("proguard-rules.pro") + + buildConfigField("boolean", "ALLOW_GLIDE_LOGS", project.properties.get("EXPO_ALLOW_GLIDE_LOGS", "false")) + } + + sourceSets { + main { + java { + if (expoModule.safeExtGet("excludeAppGlideModule", false)) { + exclude("**/ExpoImageAppGlideModule.kt") + } + } + } + } +} + +dependencies { + def GLIDE_VERSION = "4.16.0" + + implementation 'com.facebook.react:react-android' + + api "com.github.bumptech.glide:glide:${GLIDE_VERSION}" + kapt "com.github.bumptech.glide:compiler:${GLIDE_VERSION}" + api 'com.caverock:androidsvg-aar:1.4' + + implementation "com.github.penfeizhou.android.animation:glide-plugin:3.0.5" + implementation "com.github.bumptech.glide:avif-integration:${GLIDE_VERSION}" + + api 'com.github.bumptech.glide:okhttp3-integration:4.11.0' + api "com.squareup.okhttp3:okhttp:${expoModule.safeExtGet("okHttpVersion", '4.9.2')}" + + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1' + implementation "jp.wasabeef:glide-transformations:4.3.0" +} diff --git a/node_modules/expo-image/android/proguard-rules.pro b/node_modules/expo-image/android/proguard-rules.pro new file mode 100644 index 00000000..689b5578 --- /dev/null +++ b/node_modules/expo-image/android/proguard-rules.pro @@ -0,0 +1,28 @@ +# https://bumptech.github.io/glide/doc/download-setup.html#proguard + +-keep public class * extends com.bumptech.glide.module.LibraryGlideModule +-keep public class * implements com.bumptech.glide.module.GlideModule +-keep class * extends com.bumptech.glide.module.AppGlideModule { + (...); +} +-keep public enum com.bumptech.glide.load.ImageHeaderParser$** { + **[] $VALUES; + public *; +} +-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder { + *** rewind(); +} + +-keep public class com.bumptech.glide.request.ThumbnailRequestCoordinator { + *; +} + +-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder + +# https://bumptech.github.io/glide/doc/configuration.html#applications + +-keep class com.bumptech.glide.GeneratedAppGlideModuleImpl + +-keep public class com.bumptech.glide.integration.webp.WebpImage { *; } +-keep public class com.bumptech.glide.integration.webp.WebpFrame { *; } +-keep public class com.bumptech.glide.integration.webp.WebpBitmapFactory { *; } diff --git a/node_modules/expo-image/android/src/main/AndroidManifest.xml b/node_modules/expo-image/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f51f5bfb --- /dev/null +++ b/node_modules/expo-image/android/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/node_modules/expo-image/android/src/main/java/com/caverock/androidsvg/SVGStyler.kt b/node_modules/expo-image/android/src/main/java/com/caverock/androidsvg/SVGStyler.kt new file mode 100644 index 00000000..a3f4e896 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/com/caverock/androidsvg/SVGStyler.kt @@ -0,0 +1,125 @@ +package com.caverock.androidsvg + +import com.caverock.androidsvg.SVG.SPECIFIED_COLOR +import com.caverock.androidsvg.SVG.SPECIFIED_FILL +import com.caverock.androidsvg.SVG.SvgElementBase + +internal fun replaceColor(paint: SVG.SvgPaint?, newColor: Int) { + if (paint is SVG.Colour && paint !== SVG.Colour.TRANSPARENT) { + paint.colour = newColor + } +} + +internal fun replaceStyles(style: SVG.Style?, newColor: Int) { + if (style == null) { + return + } + + replaceColor(style.color, newColor) + replaceColor(style.fill, newColor) + replaceColor(style.stroke, newColor) + replaceColor(style.stopColor, newColor) + replaceColor(style.solidColor, newColor) + replaceColor(style.viewportFill, newColor) +} + +internal fun hasStyle(element: SvgElementBase): Boolean { + if (element.style == null && element.baseStyle == null) { + return false + } + + val style = element.style + val hasColorInStyle = style != null && + ( + style.color != null || style.fill != null || style.stroke != null || + style.stroke != null || style.stopColor != null || style.solidColor != null + ) + + if (hasColorInStyle) { + return true + } + + val baseStyle = element.baseStyle ?: return false + return baseStyle.color != null || baseStyle.fill != null || baseStyle.stroke != null || + baseStyle.viewportFill != null || baseStyle.stopColor != null || baseStyle.solidColor != null +} + +internal fun defineStyles(element: SvgElementBase, newColor: Int, hasStyle: Boolean) { + if (hasStyle) { + return + } + + val style = if (element.style != null) { + element.style + } else { + SVG.Style().also { + element.style = it + } + } + + val color = SVG.Colour(newColor) + when (element) { + is SVG.Path, + is SVG.Circle, + is SVG.Ellipse, + is SVG.Rect, + is SVG.SolidColor, + is SVG.Line, + is SVG.Polygon, + is SVG.PolyLine -> { + style.apply { + fill = color + + specifiedFlags = SPECIFIED_FILL + } + } + + is SVG.TextPath -> { + style.apply { + this.color = color + + specifiedFlags = SPECIFIED_COLOR + } + } + } +} + +internal fun applyTintColor(element: SVG.SvgObject, newColor: Int, parentDefinesStyle: Boolean) { + // We want to keep the colors in the mask as they control the visibility of the element to which the mask is applied. + if (element is SVG.Mask) { + return + } + + val definesStyle = if (element is SvgElementBase) { + val hasStyle = parentDefinesStyle || hasStyle(element) + + replaceStyles(element.baseStyle, newColor) + replaceStyles(element.style, newColor) + defineStyles(element, newColor, hasStyle) + + hasStyle + } else { + parentDefinesStyle + } + + if (element is SVG.SvgContainer) { + for (child in element.children) { + applyTintColor(child, newColor, definesStyle) + } + } +} + +fun applyTintColor(svg: SVG, newColor: Int) { + val root = svg.rootElement + + svg.cssRules?.forEach { rule -> + replaceStyles(rule.style, newColor) + } + replaceStyles(root.baseStyle, newColor) + replaceStyles(root.style, newColor) + val hasStyle = hasStyle(root) + + for (child in root.children) { + applyTintColor(child, newColor, hasStyle) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/CustomDownsampleStrategy.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/CustomDownsampleStrategy.kt new file mode 100644 index 00000000..00e8ca9d --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/CustomDownsampleStrategy.kt @@ -0,0 +1,226 @@ +package expo.modules.image + +import android.os.Build +import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy +import com.bumptech.glide.request.target.Target +import expo.modules.image.enums.ContentFit +import expo.modules.image.records.DecodeFormat +import kotlin.math.floor +import kotlin.math.max +import kotlin.math.min +import kotlin.math.sqrt + +/** + * Glide uses `hashCode` and `equals` of the `DownsampleStrategy` to calculate the cache key. + * However, we generate this object dynamically, which means that each instance will be different. + * Unfortunately, this behaviour is not correct since Glide will not load + * the image from memory no matter what. + * To fix this issue, we set the `hashCode` to a fixed number and + * override `equals` to only check if objects have the common type. + */ +abstract class CustomDownsampleStrategy : DownsampleStrategy() { + override fun equals(other: Any?): Boolean { + return other is CustomDownsampleStrategy + } + + override fun hashCode(): Int { + return 302008237 + } +} + +object NoopDownsampleStrategy : DownsampleStrategy() { + override fun getScaleFactor( + sourceWidth: Int, + sourceHeight: Int, + requestedWidth: Int, + requestedHeight: Int + ): Float = 1f + + override fun getSampleSizeRounding( + sourceWidth: Int, + sourceHeight: Int, + requestedWidth: Int, + requestedHeight: Int + ): SampleSizeRounding = SampleSizeRounding.QUALITY +} + +class PlaceholderDownsampleStrategy( + private val target: ImageViewWrapperTarget +) : CustomDownsampleStrategy() { + private var wasTriggered = false + + override fun getScaleFactor(sourceWidth: Int, sourceHeight: Int, requestedWidth: Int, requestedHeight: Int): Float { + if (!wasTriggered) { + target.placeholderWidth = sourceWidth + target.placeholderHeight = sourceHeight + wasTriggered = true + } + return 1f + } + + override fun getSampleSizeRounding(sourceWidth: Int, sourceHeight: Int, requestedWidth: Int, requestedHeight: Int): SampleSizeRounding { + return SampleSizeRounding.QUALITY + } +} + +class ContentFitDownsampleStrategy( + private val target: ImageViewWrapperTarget, + private val contentFit: ContentFit +) : CustomDownsampleStrategy() { + private var wasTriggered = false + override fun getScaleFactor( + sourceWidth: Int, + sourceHeight: Int, + requestedWidth: Int, + requestedHeight: Int + ): Float { + // The method is invoked twice per asset, but we only need to preserve the original dimensions for the first call. + // As Glide uses Android downsampling, it can only adjust dimensions by a factor of two, + // and hence two distinct scaling factors are computed to achieve greater accuracy. + if (!wasTriggered) { + target.sourceWidth = sourceWidth + target.sourceHeight = sourceHeight + wasTriggered = true + } + + // The size of the container is unknown, we don't know what to do, so we just run the default scale. + if (requestedWidth == Target.SIZE_ORIGINAL || requestedHeight == Target.SIZE_ORIGINAL) { + return 1f + } + + val aspectRation = calculateScaleFactor( + sourceWidth.toFloat(), + sourceHeight.toFloat(), + requestedWidth.toFloat(), + requestedHeight.toFloat() + ) + + // We don't want to upscale the image + return min(1f, aspectRation) + } + + private fun calculateScaleFactor( + sourceWidth: Float, + sourceHeight: Float, + requestedWidth: Float, + requestedHeight: Float + ): Float = when (contentFit) { + ContentFit.Contain -> min( + requestedWidth / sourceWidth, + requestedHeight / sourceHeight + ) + ContentFit.Cover -> java.lang.Float.max( + requestedWidth / sourceWidth, + requestedHeight / sourceHeight + ) + ContentFit.ScaleDown -> if (requestedWidth < sourceWidth || requestedHeight < sourceHeight) { + // The container is smaller than the image โ€” scale it down and behave like `contain` + min( + requestedWidth / sourceWidth, + requestedHeight / sourceHeight + ) + } else { + // The container is bigger than the image โ€” don't scale it and behave like `none` + 1f + } + else -> 1f + } + + override fun getSampleSizeRounding( + sourceWidth: Int, + sourceHeight: Int, + requestedWidth: Int, + requestedHeight: Int + ) = SampleSizeRounding.QUALITY +} + +/** + * Android has hardware bitmap size limit that can be drown on the canvas. + * To prevents crashes, we need to downsample the image to fit into the maximum bitmap size. + */ +class SafeDownsampleStrategy( + private val decodeFormat: DecodeFormat +) : CustomDownsampleStrategy() { + override fun getScaleFactor( + sourceWidth: Int, + sourceHeight: Int, + requestedWidth: Int, + requestedHeight: Int + ): Float { + if (maxBitmapSize <= 0) { + return 1f + } + + val sourceSize = sourceWidth * sourceHeight * decodeFormat.toBytes() + if (sourceSize <= maxBitmapSize) { + return 1f + } + + // We need to downsample the image to fit into the maximum bitmap size. + // Calculate the aspect ratio of the source image. It's always <= 1. + val srcAspectRatio = min(sourceWidth, sourceHeight).toDouble() / max(sourceWidth, sourceHeight).toDouble() + // Calculate the area of the destination image. + val dstArea = maxBitmapSize / decodeFormat.toBytes() + // Calculate the longer side of the destination image using following formulas: + // dstLongerSide * dstSmallerSide = dstArea + // srcAspectRation * dstLongerSide = dstSmallerSide + // after substitution: + // srcAspectRation * dstLongerSide * dstLongerSide = dstArea + // dstLongerSide = sqrt(dstArea / srcAspectRatio) + val x = floor(sqrt(dstArea.toDouble() / srcAspectRatio)).toInt() + + // Calculate the scale factor using longer side of both images. + val scaleFactor = x.toDouble() / max(sourceWidth, sourceHeight).toDouble() + + return scaleFactor.toFloat() + } + + override fun getSampleSizeRounding( + sourceWidth: Int, + sourceHeight: Int, + requestedWidth: Int, + requestedHeight: Int + ): SampleSizeRounding { + return SampleSizeRounding.MEMORY + } + + private val maxBitmapSize by lazy { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return@lazy -1 + } + + return@lazy try { + val defaultSize = 100 * 1024 * 1024 // 100 MB - from `RecordingCanvas` src + + // We're trying to get the value of `ro.hwui.max_texture_allocation_size` property + // which is used by `RecordingCanvas` to determine the maximum bitmap size. + @Suppress("PrivateApi") + val getIntMethod = Class + .forName("android.os.SystemProperties") + .getMethod("getInt", String::class.java, Int::class.java) + getIntMethod.isAccessible = true + (getIntMethod.invoke(null, "ro.hwui.max_texture_allocation_size", defaultSize) as Int) + .coerceAtLeast(defaultSize) + } catch (e: Throwable) { + // If something goes wrong we just return -1 and don't downsample the image. + -1 + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other !is SafeDownsampleStrategy) { + return false + } + + return decodeFormat == other.decodeFormat + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + decodeFormat.hashCode() + return result + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/CustomOptions.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/CustomOptions.kt new file mode 100644 index 00000000..dacba94a --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/CustomOptions.kt @@ -0,0 +1,8 @@ +package expo.modules.image + +import com.bumptech.glide.load.Option + +object CustomOptions { + // To pass the tint color to the SVG decoder, we need to wrap it in a custom Glide option. + val tintColor = Option.memory("ExpoTintColor") +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/Exceptions.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/Exceptions.kt new file mode 100644 index 00000000..77b093a1 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/Exceptions.kt @@ -0,0 +1,6 @@ +package expo.modules.image + +import expo.modules.kotlin.exception.CodedException + +class ImageLoadFailed(exception: Exception) : + CodedException(message = "Failed to load the image: ${exception.message}") diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageAppGlideModule.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageAppGlideModule.kt new file mode 100644 index 00000000..5735515f --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageAppGlideModule.kt @@ -0,0 +1,26 @@ +package expo.modules.image + +import android.content.Context +import android.util.Log +import com.bumptech.glide.GlideBuilder +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.module.AppGlideModule + +/** + * We need to include an [AppGlideModule] for [GlideModule] annotations + * to work. + */ +@GlideModule +class ExpoImageAppGlideModule : AppGlideModule() { + override fun applyOptions(context: Context, builder: GlideBuilder) { + super.applyOptions(context, builder) + + builder.setLogLevel( + if (BuildConfig.ALLOW_GLIDE_LOGS) { + Log.VERBOSE + } else { + Log.ERROR + } + ) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageComponentCallbacks.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageComponentCallbacks.kt new file mode 100644 index 00000000..428a56d9 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageComponentCallbacks.kt @@ -0,0 +1,22 @@ +package expo.modules.image + +import android.content.ComponentCallbacks2 +import android.content.res.Configuration +import expo.modules.image.blurhash.BlurhashDecoder + +/** + * Clears the Blurhash cache when the memory is low. + */ +object ExpoImageComponentCallbacks : ComponentCallbacks2 { + override fun onConfigurationChanged(newConfig: Configuration) = Unit + + override fun onLowMemory() { + BlurhashDecoder.clearCache() + } + + override fun onTrimMemory(level: Int) { + if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) { + onLowMemory() + } + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageModule.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageModule.kt new file mode 100644 index 00000000..f08807d0 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageModule.kt @@ -0,0 +1,341 @@ +@file:OptIn(EitherType::class) + +package expo.modules.image + +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.util.Base64 +import androidx.core.graphics.drawable.toBitmap +import androidx.core.graphics.drawable.toBitmapOrNull +import androidx.core.view.doOnDetach +import com.bumptech.glide.Glide +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.load.model.GlideUrl +import com.bumptech.glide.load.model.Headers +import com.bumptech.glide.load.model.LazyHeaders +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target +import com.github.penfeizhou.animation.apng.APNGDrawable +import com.github.penfeizhou.animation.gif.GifDrawable +import com.github.penfeizhou.animation.webp.WebPDrawable +import expo.modules.image.blurhash.BlurhashEncoder +import expo.modules.image.enums.ContentFit +import expo.modules.image.enums.Priority +import expo.modules.image.records.CachePolicy +import expo.modules.image.records.ContentPosition +import expo.modules.image.records.DecodeFormat +import expo.modules.image.records.DecodedSource +import expo.modules.image.records.ImageLoadOptions +import expo.modules.image.records.ImageTransition +import expo.modules.image.records.SourceMap +import expo.modules.image.thumbhash.ThumbhashEncoder +import expo.modules.kotlin.Promise +import expo.modules.kotlin.apifeatures.EitherType +import expo.modules.kotlin.exception.Exceptions +import expo.modules.kotlin.functions.Coroutine +import expo.modules.kotlin.functions.Queues +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition +import expo.modules.kotlin.sharedobjects.SharedRef +import expo.modules.kotlin.types.Either +import expo.modules.kotlin.types.EitherOfThree +import expo.modules.kotlin.types.toKClass +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.net.URL + +class ExpoImageModule : Module() { + override fun definition() = ModuleDefinition { + Name("ExpoImage") + + OnCreate { + appContext.reactContext?.registerComponentCallbacks(ExpoImageComponentCallbacks) + } + + OnDestroy { + appContext.reactContext?.unregisterComponentCallbacks(ExpoImageComponentCallbacks) + } + + AsyncFunction("prefetch") { urls: List, cachePolicy: CachePolicy, headersMap: Map?, promise: Promise -> + val context = appContext.reactContext ?: return@AsyncFunction false + + var imagesLoaded = 0 + var failed = false + + val headers = headersMap?.let { + LazyHeaders.Builder().apply { + it.forEach { (key, value) -> + addHeader(key, value) + } + }.build() + } ?: Headers.DEFAULT + + urls.forEach { + Glide + .with(context) + .load(GlideUrl(it, headers)) // Use `load` instead of `download` to store the asset in the memory cache + // We added `quality` and `downsample` to create the same cache key as in final image load. + .encodeQuality(100) + .downsample(NoopDownsampleStrategy) + .customize(`when` = cachePolicy == CachePolicy.MEMORY) { + diskCacheStrategy(DiskCacheStrategy.NONE) + } + .listener(object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target, + isFirstResource: Boolean + ): Boolean { + if (!failed) { + failed = true + promise.resolve(false) + } + return true + } + + override fun onResourceReady( + resource: Drawable, + model: Any, + target: Target, + dataSource: DataSource, + isFirstResource: Boolean + ): Boolean { + imagesLoaded++ + + if (imagesLoaded == urls.size) { + promise.resolve(true) + } + return true + } + }) + .submit() + } + } + + AsyncFunction("loadAsync") Coroutine { source: SourceMap, options: ImageLoadOptions? -> + ImageLoadTask(appContext, source, options ?: ImageLoadOptions()).load() + } + + suspend fun generatePlaceholder( + source: Either, + encoder: (Bitmap) -> String + ): String { + val image = source.let { + if (it.`is`(Image::class)) { + it.get(Image::class) + } else { + ImageLoadTask(appContext, SourceMap(uri = it.get(URL::class).toString()), ImageLoadOptions()).load() + } + } + return withContext(Dispatchers.Default) { + encoder(image.ref.toBitmap()) + } + } + + AsyncFunction("generateBlurhashAsync") Coroutine { source: Either, numberOfComponents: Pair -> + generatePlaceholder(source) { bitmap -> + BlurhashEncoder.encode(bitmap, numberOfComponents) + } + } + + AsyncFunction("generateThumbhashAsync") Coroutine { source: Either -> + generatePlaceholder(source) { bitmap -> + Base64.encodeToString( + ThumbhashEncoder.encode(bitmap), + Base64.NO_WRAP + ) + } + } + + Class(Image::class) { + Property("width") { image: Image -> + image.ref.intrinsicWidth + } + Property("height") { image: Image -> + image.ref.intrinsicHeight + } + Property("scale") { image: Image -> + // Not relying on `2x` in the filename, but want to make the following true: + // If you multiply the logical size of the image by this value, you get the dimensions of the image in pixels. + val screenDensity = appContext.reactContext?.resources?.displayMetrics?.density ?: 1f + (image.ref.toBitmapOrNull()?.density ?: 1) / (screenDensity * 160.0f) + } + Property("isAnimated") { image: Image -> + if (image.ref is GifDrawable) { + return@Property true + } + if (image.ref is APNGDrawable) { + return@Property true + } + if (image.ref is WebPDrawable) { + return@Property true + } + false + } + Property("mediaType") { -> + null // not easily supported on Android https://github.com/bumptech/glide/issues/1378#issuecomment-236879983 + } + } + + AsyncFunction("clearMemoryCache") { + val activity = appContext.currentActivity ?: return@AsyncFunction false + Glide.get(activity).clearMemory() + return@AsyncFunction true + }.runOnQueue(Queues.MAIN) + + AsyncFunction("clearDiskCache") { + val activity = appContext.currentActivity ?: return@AsyncFunction false + activity.let { + Glide.get(activity).clearDiskCache() + } + + return@AsyncFunction true + } + + AsyncFunction("getCachePathAsync") { cacheKey: String -> + val context = appContext.reactContext ?: return@AsyncFunction null + + val glideUrl = GlideUrl(cacheKey) + val target = Glide.with(context).asFile().load(glideUrl).onlyRetrieveFromCache(true).submit() + + return@AsyncFunction try { + val file = target.get() + file.absolutePath + } catch (_: Exception) { + null + } + } + + View(ExpoImageViewWrapper::class) { + Events( + "onLoadStart", + "onProgress", + "onError", + "onLoad", + "onDisplay" + ) + + Prop("source") { view: ExpoImageViewWrapper, sources: EitherOfThree, SharedRef, SharedRef>? -> + if (sources == null) { + view.sources = emptyList() + return@Prop + } + + if (sources.`is`(toKClass>())) { + view.sources = sources.get(toKClass>()) + return@Prop + } + + if (sources.`is`(toKClass>())) { + val drawable = sources.get(toKClass>()).ref + view.sources = listOf(DecodedSource(drawable)) + return@Prop + } + + val bitmap = sources.get(toKClass>()).ref + val context = appContext.reactContext ?: throw Exceptions.ReactContextLost() + val drawable = BitmapDrawable(context.resources, bitmap) + view.sources = listOf(DecodedSource(drawable)) + } + + Prop("contentFit") { view: ExpoImageViewWrapper, contentFit: ContentFit? -> + view.contentFit = contentFit ?: ContentFit.Cover + } + + Prop("placeholderContentFit") { view: ExpoImageViewWrapper, placeholderContentFit: ContentFit? -> + view.placeholderContentFit = placeholderContentFit ?: ContentFit.ScaleDown + } + + Prop("contentPosition") { view: ExpoImageViewWrapper, contentPosition: ContentPosition? -> + view.contentPosition = contentPosition ?: ContentPosition.center + } + + Prop("blurRadius") { view: ExpoImageViewWrapper, blurRadius: Int? -> + view.blurRadius = blurRadius?.takeIf { it > 0 } + } + + Prop("transition") { view: ExpoImageViewWrapper, transition: ImageTransition? -> + view.transition = transition + } + + Prop("tintColor") { view: ExpoImageViewWrapper, color: Int? -> + view.tintColor = color + } + + Prop("placeholder") { view: ExpoImageViewWrapper, placeholder: List? -> + view.placeholders = placeholder ?: emptyList() + } + + Prop("accessible") { view: ExpoImageViewWrapper, accessible: Boolean? -> + view.accessible = accessible == true + } + + Prop("accessibilityLabel") { view: ExpoImageViewWrapper, accessibilityLabel: String? -> + view.accessibilityLabel = accessibilityLabel + } + + Prop("focusable") { view: ExpoImageViewWrapper, isFocusable: Boolean? -> + view.isFocusableProp = isFocusable == true + } + + Prop("priority") { view: ExpoImageViewWrapper, priority: Priority? -> + view.priority = priority ?: Priority.NORMAL + } + + Prop("cachePolicy") { view: ExpoImageViewWrapper, cachePolicy: CachePolicy? -> + view.cachePolicy = cachePolicy ?: CachePolicy.DISK + } + + Prop("recyclingKey") { view: ExpoImageViewWrapper, recyclingKey: String? -> + view.recyclingKey = recyclingKey + } + + Prop("allowDownscaling") { view: ExpoImageViewWrapper, allowDownscaling: Boolean? -> + view.allowDownscaling = allowDownscaling != false + } + + Prop("autoplay") { view: ExpoImageViewWrapper, autoplay: Boolean? -> + view.autoplay = autoplay != false + } + + Prop("decodeFormat") { view: ExpoImageViewWrapper, format: DecodeFormat? -> + view.decodeFormat = format ?: DecodeFormat.ARGB_8888 + } + + AsyncFunction("startAnimating") { view: ExpoImageViewWrapper -> + view.setIsAnimating(true) + } + + AsyncFunction("stopAnimating") { view: ExpoImageViewWrapper -> + view.setIsAnimating(false) + } + + AsyncFunction("lockResourceAsync") { view: ExpoImageViewWrapper -> + view.lockResource = true + } + + AsyncFunction("unlockResourceAsync") { view: ExpoImageViewWrapper -> + view.lockResource = false + } + + AsyncFunction("reloadAsync") { view: ExpoImageViewWrapper -> + view.shouldRerender = true + view.rerenderIfNeeded(force = true) + } + + OnViewDidUpdateProps { view: ExpoImageViewWrapper -> + view.rerenderIfNeeded() + } + + OnViewDestroys { view: ExpoImageViewWrapper -> + view.doOnDetach { + view.onViewDestroys() + } + } + } + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageView.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageView.kt new file mode 100644 index 00000000..3cdf437d --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageView.kt @@ -0,0 +1,129 @@ +package expo.modules.image + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.PorterDuff +import android.graphics.RectF +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.util.Log +import androidx.appcompat.widget.AppCompatImageView +import androidx.core.graphics.transform +import androidx.core.view.isVisible +import com.facebook.react.common.annotations.UnstableReactNativeAPI +import expo.modules.image.enums.ContentFit +import expo.modules.image.records.ContentPosition + +@OptIn(UnstableReactNativeAPI::class) +@SuppressLint("ViewConstructor") +class ExpoImageView( + context: Context +) : AppCompatImageView(context) { + var currentTarget: ImageViewWrapperTarget? = null + var isPlaceholder: Boolean = false + + fun recycleView(): ImageViewWrapperTarget? { + setImageDrawable(null) + + val target = currentTarget?.apply { + isUsed = false + } + + currentTarget = null + isVisible = false + isPlaceholder = false + + return target + } + + private var transformationMatrixChanged = false + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + applyTransformationMatrix() + } + + fun applyTransformationMatrix() { + if (drawable == null) { + return + } + + if (isPlaceholder) { + applyTransformationMatrix( + drawable, + placeholderContentFit, + sourceHeight = currentTarget?.placeholderHeight, + sourceWidth = currentTarget?.placeholderWidth + ) + } else { + applyTransformationMatrix(drawable, contentFit, contentPosition) + } + } + + private fun applyTransformationMatrix( + drawable: Drawable, + contentFit: ContentFit, + contentPosition: ContentPosition = ContentPosition.center, + sourceWidth: Int? = currentTarget?.sourceWidth, + sourceHeight: Int? = currentTarget?.sourceHeight + ) { + val imageRect = RectF(0f, 0f, drawable.intrinsicWidth.toFloat(), drawable.intrinsicHeight.toFloat()) + val viewRect = RectF(0f, 0f, width.toFloat(), height.toFloat()) + + val matrix = contentFit.toMatrix( + imageRect, + viewRect, + sourceWidth ?: -1, + sourceHeight ?: -1 + ) + val scaledImageRect = imageRect.transform(matrix) + + imageMatrix = matrix.apply { + contentPosition.apply(this, scaledImageRect, viewRect) + } + } + + init { + clipToOutline = true + scaleType = ScaleType.MATRIX + } + + // region Component Props + internal var contentFit: ContentFit = ContentFit.Cover + set(value) { + field = value + transformationMatrixChanged = true + } + + internal var placeholderContentFit: ContentFit = ContentFit.ScaleDown + set(value) { + field = value + transformationMatrixChanged = true + } + + internal var contentPosition: ContentPosition = ContentPosition.center + set(value) { + field = value + transformationMatrixChanged = true + } + + internal fun setTintColor(color: Int?) { + color?.let { setColorFilter(it, PorterDuff.Mode.SRC_IN) } ?: clearColorFilter() + } + + override fun draw(canvas: Canvas) { + // If we encounter a recycled bitmap here, it suggests an issue where we may have failed to + // finish clearing the image bitmap before the UI attempts to display it. + // One solution could be to suppress the error and assume that the second image view is currently responsible for displaying the correct view. + if ((drawable as? BitmapDrawable)?.bitmap?.isRecycled == true) { + Log.e("ExpoImage", "Trying to use a recycled bitmap") + recycleView()?.let { target -> + (parent as? ExpoImageViewWrapper)?.requestManager?.let { requestManager -> + target.clear(requestManager) + } + } + } + super.draw(canvas) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageViewWrapper.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageViewWrapper.kt new file mode 100644 index 00000000..5de34df2 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/ExpoImageViewWrapper.kt @@ -0,0 +1,647 @@ +package expo.modules.image + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.graphics.drawable.Animatable +import android.graphics.drawable.Drawable +import android.os.Build +import android.os.Handler +import android.view.View +import android.widget.FrameLayout +import androidx.core.view.AccessibilityDelegateCompat +import androidx.core.view.ViewCompat +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat +import androidx.core.view.isVisible +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestManager +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy +import com.bumptech.glide.request.RequestOptions +import com.github.penfeizhou.animation.gif.GifDrawable +import expo.modules.image.enums.ContentFit +import expo.modules.image.enums.Priority +import expo.modules.image.events.GlideRequestListener +import expo.modules.image.events.OkHttpProgressListener +import expo.modules.image.okhttp.GlideUrlWrapper +import expo.modules.image.records.CachePolicy +import expo.modules.image.records.ContentPosition +import expo.modules.image.records.DecodeFormat +import expo.modules.image.records.ImageErrorEvent +import expo.modules.image.records.ImageLoadEvent +import expo.modules.image.records.ImageProgressEvent +import expo.modules.image.records.ImageTransition +import expo.modules.image.records.Source +import expo.modules.image.svg.SVGPictureDrawable +import expo.modules.kotlin.AppContext +import expo.modules.kotlin.tracing.beginAsyncTraceBlock +import expo.modules.kotlin.tracing.trace +import expo.modules.kotlin.viewevent.EventDispatcher +import expo.modules.kotlin.views.ExpoView +import jp.wasabeef.glide.transformations.BlurTransformation +import java.lang.ref.WeakReference +import kotlin.math.abs +import kotlin.math.min + +@SuppressLint("ViewConstructor") +class ExpoImageViewWrapper(context: Context, appContext: AppContext) : ExpoView(context, appContext) { + private val activity: Activity + get() = appContext.throwingActivity + + internal val requestManager = getOrCreateRequestManager(appContext, activity) + private val progressListener = OkHttpProgressListener(WeakReference(this)) + + private val firstView = ExpoImageView(activity) + private val secondView = ExpoImageView(activity) + + private val mainHandler = Handler(context.mainLooper) + + /** + * @returns the view which is currently active or will be used when both views are empty + */ + private val activeView: ExpoImageView + get() { + if (secondView.drawable != null) { + return secondView + } + return firstView + } + + private var firstTarget = ImageViewWrapperTarget(WeakReference(this)) + private var secondTarget = ImageViewWrapperTarget(WeakReference(this)) + + internal val onLoadStart by EventDispatcher() + internal val onProgress by EventDispatcher() + internal val onError by EventDispatcher() + internal val onLoad by EventDispatcher() + internal val onDisplay by EventDispatcher() + + internal var sources: List = emptyList() + private val bestSource: Source? + get() = getBestSource(sources) + + internal var placeholders: List = emptyList() + private val bestPlaceholder: Source? + get() = getBestSource(placeholders) + + internal var blurRadius: Int? = null + set(value) { + if (field != value) { + shouldRerender = true + } + field = value + } + + internal var transition: ImageTransition? = null + + internal var contentFit: ContentFit = ContentFit.Cover + set(value) { + field = value + activeView.contentFit = value + transformationMatrixChanged = true + } + + internal var placeholderContentFit: ContentFit = ContentFit.ScaleDown + set(value) { + field = value + activeView.placeholderContentFit = value + transformationMatrixChanged = true + } + + internal var contentPosition: ContentPosition = ContentPosition.center + set(value) { + field = value + activeView.contentPosition = value + transformationMatrixChanged = true + } + + internal var tintColor: Int? = null + set(value) { + field = value + // To apply the tint color to the SVG, we need to recreate the drawable. + if (activeView.drawable is SVGPictureDrawable) { + shouldRerender = true + } else { + activeView.setTintColor(value) + } + } + + internal var isFocusableProp: Boolean = false + set(value) { + field = value + activeView.isFocusable = value + } + + internal var accessible: Boolean = false + set(value) { + field = value + setIsScreenReaderFocusable(activeView, value) + } + + internal var accessibilityLabel: String? = null + set(value) { + field = value + activeView.contentDescription = accessibilityLabel + } + + var recyclingKey: String? = null + set(value) { + clearViewBeforeChangingSource = field != null && value != null && value != field + field = value + } + + internal var allowDownscaling: Boolean = true + set(value) { + field = value + shouldRerender = true + } + + internal var decodeFormat: DecodeFormat = DecodeFormat.ARGB_8888 + set(value) { + field = value + shouldRerender = true + } + + internal var autoplay: Boolean = true + + internal var lockResource: Boolean = false + + internal var priority: Priority = Priority.NORMAL + internal var cachePolicy: CachePolicy = CachePolicy.DISK + + fun setIsAnimating(setAnimating: Boolean) { + // Animatable animations always start from the beginning when resumed. + // So we check first if the resource is a GifDrawable, because it can continue + // from where it was paused. + when (val resource = activeView.drawable) { + is GifDrawable -> setIsAnimating(resource, setAnimating) + is Animatable -> setIsAnimating(resource, setAnimating) + } + } + + private fun setIsAnimating(resource: GifDrawable, setAnimating: Boolean) { + if (setAnimating) { + if (resource.isPaused) { + resource.resume() + } else { + resource.start() + } + } else { + resource.pause() + } + } + + private fun setIsAnimating(resource: Animatable, setAnimating: Boolean) { + if (setAnimating) { + resource.start() + } else { + resource.stop() + } + } + + /** + * Whether the image should be loaded again + */ + internal var shouldRerender = false + + /** + * Currently loaded source + */ + private var loadedSource: GlideModelProvider? = null + + /** + * Whether the transformation matrix should be reapplied + */ + private var transformationMatrixChanged = false + + /** + * Whether the view content should be cleared to blank when the source was changed. + */ + private var clearViewBeforeChangingSource = false + + /** + * Copies saved props to the provided view. + * It ensures that the view state is up to date. + */ + private fun copyProps(view: ExpoImageView) { + view.contentFit = contentFit + view.contentPosition = contentPosition + view.setTintColor(tintColor) + view.isFocusable = isFocusableProp + view.contentDescription = accessibilityLabel + setIsScreenReaderFocusable(view, accessible) + } + + /** + * Allows `isScreenReaderFocusable` to be set on apis below level 28 + */ + private fun setIsScreenReaderFocusable(view: View, value: Boolean) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + view.isScreenReaderFocusable = value + } else { + ViewCompat.setAccessibilityDelegate( + this, + object : AccessibilityDelegateCompat() { + override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) { + info.isScreenReaderFocusable = value + super.onInitializeAccessibilityNodeInfo(host, info) + } + } + ) + } + } + + /** + * When a new resource is available, this method tries to handle it. + * It decides where provided bitmap should be displayed and clears the previous target/image. + */ + fun onResourceReady( + target: ImageViewWrapperTarget, + resource: Drawable, + isPlaceholder: Boolean = false + ) = + // The "onResourceReady" function will be triggered when the new resource is available by the Glide. + // According to the Glide documentation (https://bumptech.github.io/glide/doc/debugging.html#you-cant-start-or-clear-loads-in-requestlistener-or-target-callbacks), + // it's not advisable to clear the Glide target within the stack frame. + // To avoid this, a new runnable is posted to the front of the main queue, which can then clean or create targets. + // This ensures that the "onResourceReady" frame of the Glide code will be discarded, and the internal state can be altered once again. + // Normally, using "postAtFrontOfQueue" can lead to issues such as message queue starvation, ordering problems, and other unexpected consequences. + // However, in this case, it is safe to use as long as nothing else is added to the queue. + // The intention is simply to wait for the Glide code to finish before the content of the underlying views is changed during the same rendering tick. + mainHandler.postAtFrontOfQueue { + trace(Trace.tag, "onResourceReady") { + val transitionDuration = (transition?.duration ?: 0).toLong() + + // If provided resource is a placeholder, but the target doesn't have a source, we treat it as a normal image. + if (!isPlaceholder || !target.hasSource) { + val (newView, previousView) = if (firstView.drawable == null) { + firstView to secondView + } else { + secondView to firstView + } + + val clearPreviousView = { + previousView + .recycleView() + ?.apply { + // When the placeholder is loaded, one target is displayed in both views. + // So we just have to move the reference to a new view instead of clearing the target. + if (this != target) { + clear(requestManager) + } + } + } + + configureView(newView, target, resource, isPlaceholder) + + // Dispatch "onDisplay" event only for the main source (no placeholder). + if (target.hasSource) { + onDisplay.invoke(Unit) + } + + if (transitionDuration <= 0) { + clearPreviousView() + newView.alpha = 1f + newView.bringToFront() + } else { + newView.bringToFront() + previousView.alpha = 1f + newView.alpha = 0f + previousView.animate().apply { + duration = transitionDuration + alpha(0f) + withEndAction { + clearPreviousView() + } + } + newView.animate().apply { + duration = transitionDuration + alpha(1f) + } + } + } else { + // We don't want to show the placeholder if something is currently displayed. + // There is one exception - when we're displaying a different placeholder. + if ((firstView.drawable != null && !firstView.isPlaceholder) || secondView.drawable != null) { + return@trace + } + + firstView + .recycleView() + ?.apply { + // The current target is already bound to the view. We don't want to cancel it in that case. + if (this != target) { + clear(requestManager) + } + } + + configureView(firstView, target, resource, isPlaceholder) + if (transitionDuration > 0) { + firstView.bringToFront() + firstView.alpha = 0f + secondView.isVisible = false + firstView.animate().apply { + duration = transitionDuration + alpha(1f) + } + } + } + + // If our image is animated, we want to see if autoplay is disabled. If it is, we should + // stop the animation as soon as the resource is ready. Placeholders should not follow this + // value since the intention is almost certainly to display the animation (i.e. a spinner) + if (resource is Animatable && !isPlaceholder && !autoplay) { + resource.stop() + } + } + } + + private fun configureView( + view: ExpoImageView, + target: ImageViewWrapperTarget, + resource: Drawable, + isPlaceholder: Boolean + ) { + view.let { + it.setImageDrawable(resource) + + it.isPlaceholder = isPlaceholder + it.placeholderContentFit = target.placeholderContentFit ?: ContentFit.ScaleDown + copyProps(it) + + it.isVisible = true + + it.currentTarget = target + + // The view isn't layout when it's invisible. + // Therefore, we have to set the correct size manually. + it.layout(0, 0, width, height) + + it.applyTransformationMatrix() + } + target.isUsed = true + + if (resource is Animatable) { + resource.start() + } + } + + private fun getBestSource(sources: List): Source? { + if (sources.isEmpty()) { + return null + } + + if (sources.size == 1) { + return sources.first() + } + + val targetPixelCount = width * height + if (targetPixelCount == 0) { + return null + } + + var bestSource: Source? = null + var bestFit = Double.MAX_VALUE + + sources.forEach { + val fit = abs(1 - (it.pixelCount / targetPixelCount)) + if (fit < bestFit) { + bestFit = fit + bestSource = it + } + } + + return bestSource + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + rerenderIfNeeded( + shouldRerenderBecauseOfResize = allowDownscaling && + contentFit != ContentFit.Fill && + contentFit != ContentFit.None + ) + } + + private fun createPropOptions(): RequestOptions { + return RequestOptions() + .priority(this@ExpoImageViewWrapper.priority.toGlidePriority()) + .customize(`when` = cachePolicy != CachePolicy.MEMORY_AND_DISK && cachePolicy != CachePolicy.MEMORY) { + skipMemoryCache(true) + } + .customize(`when` = cachePolicy == CachePolicy.NONE || cachePolicy == CachePolicy.MEMORY) { + diskCacheStrategy(DiskCacheStrategy.NONE) + } + .customize(blurRadius) { + transform(BlurTransformation(min(it, 25), 4)) + } + } + + fun onViewDestroys() { + firstView.setImageDrawable(null) + secondView.setImageDrawable(null) + + requestManager.clear(firstTarget) + requestManager.clear(secondTarget) + } + + private fun cleanIfNeeded( + newBestSource: Source?, + newBestSourceModel: GlideModelProvider?, + newBestPlaceholderModel: GlideModelProvider? + ): Boolean { + // We only clean the image when the source is set to null and we don't have a placeholder or the view is empty. + if (width == 0 || height == 0 || (newBestSource == null || newBestSourceModel == null) && newBestPlaceholderModel == null) { + firstView.recycleView() + secondView.recycleView() + + requestManager.clear(firstTarget) + requestManager.clear(secondTarget) + + shouldRerender = false + loadedSource = null + transformationMatrixChanged = false + clearViewBeforeChangingSource = false + return true + } + + return false + } + + private fun createDownsampleStrategy(target: ImageViewWrapperTarget): DownsampleStrategy { + return if (!allowDownscaling) { + DownsampleStrategy.NONE + } else if ( + contentFit != ContentFit.Fill && + contentFit != ContentFit.None + ) { + ContentFitDownsampleStrategy(target, contentFit) + } else { + // it won't downscale the image if the image is smaller than hardware bitmap size limit + SafeDownsampleStrategy(decodeFormat) + } + } + + private fun clearViewBeforeChangingSource() { + if (clearViewBeforeChangingSource) { + val activeView = if (firstView.drawable != null) { + firstView + } else { + secondView + } + + activeView + .recycleView() + ?.apply { + clear(requestManager) + } + } + } + + internal fun rerenderIfNeeded(shouldRerenderBecauseOfResize: Boolean = false, force: Boolean = false) = + trace(Trace.tag, "rerenderIfNeeded(shouldRerenderBecauseOfResize=$shouldRerenderBecauseOfResize,force=$force)") { + if (lockResource && !force) { + return@trace + } + val bestSource = bestSource + val bestPlaceholder = bestPlaceholder + + val sourceToLoad = bestSource?.createGlideModelProvider(context) + val placeholder = bestPlaceholder?.createGlideModelProvider(context) + + if (cleanIfNeeded(bestSource, sourceToLoad, placeholder)) { + // the view was cleaned + return@trace + } + + val shouldRerender = sourceToLoad != loadedSource || shouldRerender || (sourceToLoad == null && placeholder != null) + if (!shouldRerender && !shouldRerenderBecauseOfResize) { + // In the case where the source didn't change, but the transformation matrix has to be + // recalculated, we can apply the new transformation right away. + // When the source and the matrix is different, we don't want to do anything. + // We don't want to changed the transformation of the currently displayed image. + // The new matrix will be applied when new resource is loaded. + if (transformationMatrixChanged) { + activeView.applyTransformationMatrix() + } + + transformationMatrixChanged = false + clearViewBeforeChangingSource = false + + return@trace + } + + clearViewBeforeChangingSource() + + this.shouldRerender = false + loadedSource = sourceToLoad + val options = bestSource?.createGlideOptions(context) + val propOptions = createPropOptions() + + val model = sourceToLoad?.getGlideModel() + if (model is GlideUrlWrapper) { + model.progressListener = progressListener + } + + onLoadStart.invoke(Unit) + val newTarget = if (secondTarget.isUsed) { + firstTarget + } else { + secondTarget + } + newTarget.hasSource = sourceToLoad != null + + val downsampleStrategy = createDownsampleStrategy(newTarget) + + val request = requestManager + .asDrawable() + .load(model) + .customize(bestPlaceholder, placeholder) { placeholderSource, placeholderModel -> + val newPlaceholderContentFit = if (!placeholderSource.usesPlaceholderContentFit()) { + contentFit + } else { + placeholderContentFit + } + newTarget.placeholderContentFit = newPlaceholderContentFit + + thumbnail( + requestManager.load(placeholderModel.getGlideModel()) + .downsample(PlaceholderDownsampleStrategy(newTarget)) + .apply(placeholderSource.createGlideOptions(context)) + ) + } + .downsample(downsampleStrategy) + .addListener(GlideRequestListener(WeakReference(this))) + .encodeQuality(100) + .format(decodeFormat.toGlideFormat()) + .apply(propOptions) + .apply(options) + .customize(tintColor) { + apply(RequestOptions().set(CustomOptions.tintColor, it)) + } + + val cookie = Trace.getNextCookieValue() + beginAsyncTraceBlock(Trace.tag, Trace.loadNewImageBlock, cookie) + newTarget.setCookie(cookie) + request.into(newTarget) + + transformationMatrixChanged = false + clearViewBeforeChangingSource = false + } + + init { + val matchParent = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + + layoutParams = matchParent + + firstView.isVisible = true + secondView.isVisible = true + + // We need to add a `FrameLayout` to allow views to overflow. + // With the `LinearLayout` is impossible to render two views on each other. + val layout = FrameLayout(context).apply { + layoutParams = matchParent + addView( + firstView, + matchParent + ) + addView( + secondView, + matchParent + ) + } + + addView(layout, matchParent) + } + + companion object { + private var requestManager: RequestManager? = null + private var appContextRef: WeakReference = WeakReference(null) + private var activityRef: WeakReference = WeakReference(null) + + fun getOrCreateRequestManager( + appContext: AppContext, + activity: Activity + ): RequestManager = synchronized(Companion) { + val cachedRequestManager = requestManager + ?: return createNewRequestManager(activity).also { + requestManager = it + appContextRef = WeakReference(appContext) + activityRef = WeakReference(activity) + } + + // Request manager was created using different activity or app context + if (appContextRef.get() != appContext || activityRef.get() != activity) { + return createNewRequestManager(activity).also { + requestManager = it + appContextRef = WeakReference(appContext) + activityRef = WeakReference(activity) + } + } + + return cachedRequestManager + } + + private fun createNewRequestManager(activity: Activity): RequestManager = Glide.with(activity) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/GlideExtensions.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/GlideExtensions.kt new file mode 100644 index 00000000..eb678bca --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/GlideExtensions.kt @@ -0,0 +1,67 @@ +package expo.modules.image + +import com.bumptech.glide.RequestBuilder +import com.bumptech.glide.request.RequestOptions + +/** + * Conditionally applies the block to the RequestBuilder if the condition is true. + */ +fun RequestBuilder.customize(`when`: Boolean, block: RequestBuilder.() -> RequestBuilder): RequestBuilder { + if (!`when`) { + return this + } + + return block() +} + +/** + * Conditionally applies the block to the RequestBuilder if the value is not null. + */ +inline fun RequestBuilder.customize(value: P?, block: RequestBuilder.(P) -> RequestBuilder): RequestBuilder { + if (value == null) { + return this + } + + return block(value) +} + +/** + * Conditionally applies the block to the RequestBuilder if both values aren't null. + */ +inline fun RequestBuilder.customize(first: P1?, second: P2?, block: RequestBuilder.(P1, P2) -> RequestBuilder): RequestBuilder { + if (first == null || second == null) { + return this + } + + return block(first, second) +} + +/** + * Conditionally applies the block to the RequestOptions if the condition is true. + */ +inline fun RequestOptions.customize(`when`: Boolean, block: RequestOptions.() -> RequestOptions): RequestOptions { + if (!`when`) { + return this + } + + return block() +} + +/** + * Conditionally applies the block to the RequestOptions if the value is not null. + */ +inline fun RequestOptions.customize(value: T?, block: RequestOptions.(T) -> RequestOptions): RequestOptions { + if (value == null) { + return this + } + + return block(value) +} + +fun RequestBuilder.apply(options: RequestOptions?): RequestBuilder { + if (options == null) { + return this + } + + return apply(options) +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/GlideModelProvider.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/GlideModelProvider.kt new file mode 100644 index 00000000..617dbf9a --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/GlideModelProvider.kt @@ -0,0 +1,51 @@ +package expo.modules.image + +import android.graphics.drawable.Drawable +import android.net.Uri +import com.bumptech.glide.load.model.GlideUrl +import expo.modules.image.blurhash.BlurhashModel +import expo.modules.image.decodedsource.DecodedModel +import expo.modules.image.okhttp.GlideUrlWrapper +import expo.modules.image.thumbhash.ThumbhashModel + +fun interface GlideModelProvider { + fun getGlideModel(): Any +} + +data class DecodedModelProvider( + private val drawable: Drawable +) : GlideModelProvider { + override fun getGlideModel() = DecodedModel(drawable) +} + +data class UrlModelProvider( + private val glideUrl: GlideUrl +) : GlideModelProvider { + override fun getGlideModel() = GlideUrlWrapper(glideUrl) +} + +data class RawModelProvider( + private val data: String +) : GlideModelProvider { + override fun getGlideModel() = data +} + +data class UriModelProvider( + private val uri: Uri +) : GlideModelProvider { + override fun getGlideModel() = uri +} + +data class BlurhashModelProvider( + private val uri: Uri, + private val width: Int, + private val height: Int +) : GlideModelProvider { + override fun getGlideModel() = BlurhashModel(uri, width, height) +} + +data class ThumbhashModelProvider( + private val uri: Uri +) : GlideModelProvider { + override fun getGlideModel() = ThumbhashModel(uri) +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/Image.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/Image.kt new file mode 100644 index 00000000..f66c9445 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/Image.kt @@ -0,0 +1,20 @@ +package expo.modules.image + +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import expo.modules.kotlin.sharedobjects.SharedRef + +class Image(ref: Drawable) : SharedRef(ref) { + override val nativeRefType: String = "image" + + override fun getAdditionalMemoryPressure(): Int { + val ref = ref + if (ref is BitmapDrawable) { + return ref.getBitmap().allocationByteCount + } + + // We can't get the size in bytes of the drawable. + // Let's just return the size in pixels for now. + return ref.intrinsicWidth * ref.intrinsicHeight + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/ImageLoadTask.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/ImageLoadTask.kt new file mode 100644 index 00000000..16f84294 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/ImageLoadTask.kt @@ -0,0 +1,39 @@ +package expo.modules.image + +import com.bumptech.glide.Glide +import expo.modules.image.records.ImageLoadOptions +import expo.modules.image.records.SourceMap +import expo.modules.kotlin.AppContext +import expo.modules.kotlin.exception.Exceptions +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +open class ImageLoadTask( + private val appContext: AppContext, + private val source: SourceMap, + private val options: ImageLoadOptions +) { + suspend fun load(): Image { + val context = + this@ImageLoadTask.appContext.reactContext ?: throw Exceptions.ReactContextLost() + + val sourceToLoad = source.createGlideModelProvider(context) + val model = sourceToLoad?.getGlideModel() + + try { + val bitmap = withContext(Dispatchers.IO) { + Glide + .with(context) + .asDrawable() + .load(model) + .centerInside() + .submit(options.maxWidth, options.maxHeight) + .get() + } + + return Image(bitmap) + } catch (e: Exception) { + throw ImageLoadFailed(e) + } + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/ImageUtils.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/ImageUtils.kt new file mode 100644 index 00000000..a675efdc --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/ImageUtils.kt @@ -0,0 +1,42 @@ +package expo.modules.image + +import android.graphics.RectF + +fun calcXTranslation( + value: Float, + imageRect: RectF, + viewRect: RectF, + isPercentage: Boolean = false, + isReverse: Boolean = false +): Float = calcTranslation(value, imageRect.width(), viewRect.width(), isPercentage, isReverse) + +fun calcYTranslation( + value: Float, + imageRect: RectF, + viewRect: RectF, + isPercentage: Boolean = false, + isReverse: Boolean = false +): Float = calcTranslation(value, imageRect.height(), viewRect.height(), isPercentage, isReverse) + +fun calcTranslation( + value: Float, + imageRefValue: Float, + viewRefValue: Float, + isPercentage: Boolean = false, + isReverse: Boolean = false +): Float { + if (isPercentage) { + val finalPercentage = if (isReverse) { + 100f - value + } else { + value + } + return (finalPercentage / 100f) * (viewRefValue - imageRefValue) + } + + if (isReverse) { + return viewRefValue - imageRefValue - value + } + + return value +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/ImageViewWrapperTarget.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/ImageViewWrapperTarget.kt new file mode 100644 index 00000000..96220627 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/ImageViewWrapperTarget.kt @@ -0,0 +1,350 @@ +package expo.modules.image + +import android.content.Context +import android.graphics.Point +import android.graphics.drawable.Drawable +import android.util.Log +import android.view.ViewGroup +import android.view.ViewTreeObserver +import android.view.WindowManager +import androidx.annotation.VisibleForTesting +import com.bumptech.glide.RequestManager +import com.bumptech.glide.request.Request +import com.bumptech.glide.request.ThumbnailRequestCoordinator +import com.bumptech.glide.request.target.SizeReadyCallback +import com.bumptech.glide.request.target.Target +import com.bumptech.glide.request.transition.Transition +import com.bumptech.glide.util.Preconditions +import com.bumptech.glide.util.Synthetic +import expo.modules.core.utilities.ifNull +import expo.modules.image.enums.ContentFit +import expo.modules.kotlin.tracing.endAsyncTraceBlock +import java.lang.ref.WeakReference +import kotlin.math.max + +/** + * A custom target to provide a smooth transition between multiple drawables. + * It delegates images to the [ExpoImageViewWrapper], where we handle the loaded [Drawable]. + * When the target is cleared, we don't do anything. The [ExpoImageViewWrapper] is responsible for + * clearing bitmaps before freeing targets. That may be error-prone, but that is the only way + * of implementing the transition between bitmaps. + */ +class ImageViewWrapperTarget( + private val imageViewHolder: WeakReference +) : Target { + /** + * Whether the target has a main, non-placeholder source + */ + var hasSource = false + + /** + * Whether the target is used - the asset loaded by it has been drawn in the image view + */ + var isUsed = false + + /** + * The main source height where -1 means unknown + */ + var sourceHeight = -1 + + /** + * The main source width where -1 means unknown + */ + var sourceWidth = -1 + + /** + * The placeholder height where -1 means unknown + */ + var placeholderHeight = -1 + + /** + * The placeholder width where -1 means unknown + */ + var placeholderWidth = -1 + + private var cookie = -1 + + fun setCookie(newValue: Int) { + endLoadingNewImageTraceBlock() + synchronized(this) { + cookie = newValue + } + } + + /** + * The content fit of the placeholder + */ + var placeholderContentFit: ContentFit? = null + + private var request: Request? = null + private var sizeDeterminer = SizeDeterminer(imageViewHolder) + + private fun endLoadingNewImageTraceBlock() = synchronized(this) { + if (cookie < 0) { + return@synchronized + } + + endAsyncTraceBlock(Trace.tag, Trace.loadNewImageBlock, cookie) + cookie = -1 + } + + override fun onResourceReady(resource: Drawable, transition: Transition?) { + // The image view should always be valid. When the view is deallocated, all targets should be + // canceled. Therefore that code shouldn't be called in that case. Instead of crashing, we + // decided to ignore that. + val imageView = imageViewHolder.get().ifNull { + endLoadingNewImageTraceBlock() + Log.w("ExpoImage", "The `ExpoImageViewWrapper` was deallocated, but the target wasn't canceled in time.") + return + } + + // The thumbnail and full request are handled in the same way by Glide. + // Here we're checking if the provided resource is the final bitmap or a thumbnail. + val isPlaceholder = if (request is ThumbnailRequestCoordinator) { + (request as? ThumbnailRequestCoordinator) + ?.getPrivateFullRequest() + ?.isComplete == false + } else { + false + } + + if (!isPlaceholder) { + endLoadingNewImageTraceBlock() + } + + imageView.onResourceReady(this, resource, isPlaceholder) + } + + override fun onStart() = Unit + + override fun onStop() = Unit + + override fun onDestroy() = Unit + + override fun onLoadStarted(placeholder: Drawable?) = Unit + + // When loading fails, it's handled by the global listener, therefore that method can be NOOP. + override fun onLoadFailed(errorDrawable: Drawable?) { + endLoadingNewImageTraceBlock() + } + + override fun onLoadCleared(placeholder: Drawable?) = Unit + + override fun getSize(cb: SizeReadyCallback) { + // If we can't resolve the image, we just return unknown size. + // It shouldn't happen in a production application, because it means that our view was deallocated. + if (imageViewHolder.get() == null) { + cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + return + } + + sizeDeterminer.getSize(cb) + } + + override fun removeCallback(cb: SizeReadyCallback) { + sizeDeterminer.removeCallback(cb) + } + + override fun setRequest(request: Request?) { + this.request = request + } + + override fun getRequest() = request + + fun clear(requestManager: RequestManager) { + sizeDeterminer.clearCallbacksAndListener() + requestManager.clear(this) + } +} + +// Copied from the Glide codebase. +// We modified that to receive a weak ref to our view instead of strong one. +internal class SizeDeterminer(private val imageViewHolder: WeakReference) { + private val cbs: MutableList = ArrayList() + + @Synthetic + var waitForLayout = false + private var layoutListener: SizeDeterminerLayoutListener? = null + private fun notifyCbs(width: Int, height: Int) { + // One or more callbacks may trigger the removal of one or more additional callbacks, so we + // need a copy of the list to avoid a concurrent modification exception. One place this + // happens is when a full request completes from the in memory cache while its thumbnail is + // still being loaded asynchronously. See #2237. + for (cb in ArrayList(cbs)) { + cb.onSizeReady(width, height) + } + } + + @Synthetic + fun checkCurrentDimens() { + if (cbs.isEmpty()) { + return + } + val currentWidth = targetWidth + val currentHeight = targetHeight + if (!isViewStateAndSizeValid(currentWidth, currentHeight)) { + return + } + notifyCbs(currentWidth, currentHeight) + clearCallbacksAndListener() + } + + fun getSize(cb: SizeReadyCallback) { + val view = imageViewHolder.get() ?: return + + val currentWidth = targetWidth + val currentHeight = targetHeight + if (isViewStateAndSizeValid(currentWidth, currentHeight)) { + cb.onSizeReady(currentWidth, currentHeight) + return + } + + // We want to notify callbacks in the order they were added and we only expect one or two + // callbacks to be added a time, so a List is a reasonable choice. + if (!cbs.contains(cb)) { + cbs.add(cb) + } + if (layoutListener == null) { + val observer = view.viewTreeObserver + layoutListener = SizeDeterminerLayoutListener(this) + observer.addOnPreDrawListener(layoutListener) + } + } + + /** + * The callback may be called anyway if it is removed by another [SizeReadyCallback] or + * otherwise removed while we're notifying the list of callbacks. + * + * + * See #2237. + */ + fun removeCallback(cb: SizeReadyCallback) { + cbs.remove(cb) + } + + fun clearCallbacksAndListener() { + // Keep a reference to the layout attachStateListener and remove it here + // rather than having the observer remove itself because the observer + // we add the attachStateListener to will be almost immediately merged into + // another observer and will therefore never be alive. If we instead + // keep a reference to the attachStateListener and remove it here, we get the + // current view tree observer and should succeed. + val observer = imageViewHolder.get()?.viewTreeObserver + if (observer?.isAlive == true) { + observer.removeOnPreDrawListener(layoutListener) + } + layoutListener = null + cbs.clear() + } + + private fun isViewStateAndSizeValid(width: Int, height: Int): Boolean { + return isDimensionValid(width) && isDimensionValid(height) + } + + private val targetHeight: Int + get() { + val view = imageViewHolder.get() ?: return Target.SIZE_ORIGINAL + val verticalPadding = view.paddingTop + view.paddingBottom + val layoutParams = view.layoutParams + val layoutParamSize = layoutParams?.height ?: PENDING_SIZE + return getTargetDimen(view.height, layoutParamSize, verticalPadding) + } + private val targetWidth: Int + get() { + val view = imageViewHolder.get() ?: return Target.SIZE_ORIGINAL + val horizontalPadding = view.paddingLeft + view.paddingRight + val layoutParams = view.layoutParams + val layoutParamSize = layoutParams?.width ?: PENDING_SIZE + return getTargetDimen(view.width, layoutParamSize, horizontalPadding) + } + + private fun getTargetDimen(viewSize: Int, paramSize: Int, paddingSize: Int): Int { + val view = imageViewHolder.get() ?: return Target.SIZE_ORIGINAL + + // We consider the View state as valid if the View has non-null layout params and a non-zero + // layout params width and height. This is imperfect. We're making an assumption that View + // parents will obey their child's layout parameters, which isn't always the case. + val adjustedParamSize = paramSize - paddingSize + if (adjustedParamSize > 0) { + return adjustedParamSize + } + + // Since we always prefer layout parameters with fixed sizes, even if waitForLayout is true, + // we might as well ignore it and just return the layout parameters above if we have them. + // Otherwise we should wait for a layout pass before checking the View's dimensions. + if (waitForLayout && view.isLayoutRequested) { + return PENDING_SIZE + } + + // We also consider the View state valid if the View has a non-zero width and height. This + // means that the View has gone through at least one layout pass. It does not mean the Views + // width and height are from the current layout pass. For example, if a View is re-used in + // RecyclerView or ListView, this width/height may be from an old position. In some cases + // the dimensions of the View at the old position may be different than the dimensions of the + // View in the new position because the LayoutManager/ViewParent can arbitrarily decide to + // change them. Nevertheless, in most cases this should be a reasonable choice. + val adjustedViewSize = viewSize - paddingSize + if (adjustedViewSize > 0) { + return adjustedViewSize + } + + // Finally we consider the view valid if the layout parameter size is set to wrap_content. + // It's difficult for Glide to figure out what to do here. Although Target.SIZE_ORIGINAL is a + // coherent choice, it's extremely dangerous because original images may be much too large to + // fit in memory or so large that only a couple can fit in memory, causing OOMs. If users want + // the original image, they can always use .override(Target.SIZE_ORIGINAL). Since wrap_content + // may never resolve to a real size unless we load something, we aim for a square whose length + // is the largest screen size. That way we're loading something and that something has some + // hope of being downsampled to a size that the device can support. We also log a warning that + // tries to explain what Glide is doing and why some alternatives are preferable. + // Since WRAP_CONTENT is sometimes used as a default layout parameter, we always wait for + // layout to complete before using this fallback parameter (ConstraintLayout among others). + if (!view.isLayoutRequested && paramSize == ViewGroup.LayoutParams.WRAP_CONTENT) { + return getMaxDisplayLength(view.context) + } + + // If the layout parameters are < padding, the view size is < padding, or the layout + // parameters are set to match_parent or wrap_content and no layout has occurred, we should + // wait for layout and repeat. + return PENDING_SIZE + } + + private fun isDimensionValid(size: Int): Boolean { + return size > 0 || size == Target.SIZE_ORIGINAL + } + + private class SizeDeterminerLayoutListener(sizeDeterminer: SizeDeterminer) : ViewTreeObserver.OnPreDrawListener { + private val sizeDeterminerRef: WeakReference + + init { + sizeDeterminerRef = WeakReference(sizeDeterminer) + } + + override fun onPreDraw(): Boolean { + val sizeDeterminer = sizeDeterminerRef.get() + sizeDeterminer?.checkCurrentDimens() + return true + } + } + + companion object { + // Some negative sizes (Target.SIZE_ORIGINAL) are valid, 0 is never valid. + private const val PENDING_SIZE = 0 + + @VisibleForTesting + var maxDisplayLength: Int? = null + + // Use the maximum to avoid depending on the device's current orientation. + @Suppress("DEPRECATION") // We have copied this code from Glide and are waiting for them to remove the deprecated APIs. + private fun getMaxDisplayLength(context: Context): Int { + if (maxDisplayLength == null) { + val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + val display = Preconditions.checkNotNull(windowManager).defaultDisplay + val displayDimensions = Point() + display.getSize(displayDimensions) + maxDisplayLength = max(displayDimensions.x, displayDimensions.y) + } + return maxDisplayLength!! + } + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/ResourceIdHelper.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/ResourceIdHelper.kt new file mode 100644 index 00000000..b1085fe7 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/ResourceIdHelper.kt @@ -0,0 +1,47 @@ +package expo.modules.image + +import android.annotation.SuppressLint +import android.content.Context +import android.net.Uri +import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper +import java.util.* + +object ResourceIdHelper { + private val idMap = mutableMapOf() + + @SuppressLint("DiscouragedApi") + private fun getResourceRawId(context: Context, name: String): Int { + if (name.isEmpty()) { + return -1 + } + + val normalizedName = name.lowercase(Locale.ROOT).replace("-", "_") + synchronized(this) { + val id = idMap[normalizedName] + if (id != null) { + return id + } + + return context + .resources + .getIdentifier(normalizedName, "raw", context.packageName) + .also { + idMap[normalizedName] = it + } + } + } + + fun getResourceUri(context: Context, name: String): Uri? { + val drawableUri = ResourceDrawableIdHelper.getResourceDrawableUri(context, name) + if (drawableUri != Uri.EMPTY) { + return drawableUri + } + + val resId = getResourceRawId(context, name) + return if (resId > 0) { + Uri.Builder().scheme("res").path(resId.toString()).build() + } else { + null + } + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/ThumbnailRequestCoordinatorExtension.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/ThumbnailRequestCoordinatorExtension.kt new file mode 100644 index 00000000..ab0436a6 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/ThumbnailRequestCoordinatorExtension.kt @@ -0,0 +1,21 @@ +package expo.modules.image + +import android.util.Log +import com.bumptech.glide.request.Request +import com.bumptech.glide.request.ThumbnailRequestCoordinator + +fun ThumbnailRequestCoordinator.getPrivateFullRequest(): Request? { + return getPrivateField("full") +} + +private fun ThumbnailRequestCoordinator.getPrivateField(name: String): T? { + return try { + val field = this.javaClass.getDeclaredField(name) + field.isAccessible = true + @Suppress("UNCHECKED_CAST") + field.get(this) as T + } catch (e: Throwable) { + Log.e("ExpoImage", "Couldn't receive the `$name` field", e) + null + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/Trace.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/Trace.kt new file mode 100644 index 00000000..5dd1f142 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/Trace.kt @@ -0,0 +1,11 @@ +package expo.modules.image + +object Trace { + val tag = "ExpoImage" + val loadNewImageBlock = "load new image" + + private var lastCookieValue = 0 + fun getNextCookieValue() = synchronized(this) { + lastCookieValue++ + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/YogaUtils.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/YogaUtils.kt new file mode 100644 index 00000000..f8651adc --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/YogaUtils.kt @@ -0,0 +1,20 @@ +package expo.modules.image + +import com.facebook.yoga.YogaConstants + +fun Float.ifYogaUndefinedUse(value: Float) = + if (YogaConstants.isUndefined(this)) { + value + } else { + this + } + +inline fun Float.ifYogaDefinedUse(transformFun: (current: Float) -> Float) = + if (YogaConstants.isUndefined(this)) { + this + } else { + transformFun(this) + } + +fun makeYogaUndefinedIfNegative(value: Float) = + if (!YogaConstants.isUndefined(value) && value < 0) YogaConstants.UNDEFINED else value diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurHashFetcher.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurHashFetcher.kt new file mode 100644 index 00000000..9aaa3158 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurHashFetcher.kt @@ -0,0 +1,32 @@ +package expo.modules.image.blurhash + +import android.graphics.Bitmap +import com.bumptech.glide.Priority +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.data.DataFetcher +import expo.modules.kotlin.exception.CodedException + +class BlurhashDecodingFailure(blurHash: String?) : CodedException( + message = "Cannot decode provided blurhash '$blurHash'" +) + +class BlurHashFetcher( + private val blurHash: String?, + private val width: Int, + private val height: Int, + private val punch: Float +) : DataFetcher { + override fun cleanup() = Unit + override fun cancel() = Unit + override fun getDataClass(): Class = Bitmap::class.java + override fun getDataSource(): DataSource = DataSource.LOCAL + + override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + val bitmap = BlurhashDecoder.decode(blurHash, width, height, punch) + if (bitmap == null) { + callback.onLoadFailed(BlurhashDecodingFailure(blurHash)) + return + } + callback.onDataReady(bitmap) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashDecoder.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashDecoder.kt new file mode 100644 index 00000000..a75d8872 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashDecoder.kt @@ -0,0 +1,180 @@ +package expo.modules.image.blurhash + +import android.graphics.Bitmap +import android.graphics.Color +import kotlin.math.cos +import kotlin.math.pow +import kotlin.math.withSign + +/** + * Copied from https://github.com/woltapp/blurhash. + */ +object BlurhashDecoder { + + // cache Math.cos() calculations to improve performance. + // The number of calculations can be huge for many bitmaps: width * height * numCompX * numCompY * 2 * nBitmaps + // the cache is enabled by default, it is recommended to disable it only when just a few images are displayed + private val cacheCosinesX = HashMap() + private val cacheCosinesY = HashMap() + + /** + * Clear calculations stored in memory cache. + * The cache is not big, but will increase when many image sizes are used, + * if the app needs memory it is recommended to clear it. + */ + fun clearCache() { + cacheCosinesX.clear() + cacheCosinesY.clear() + } + + /** + * Decode a blur hash into a new bitmap. + * + * @param useCache use in memory cache for the calculated math, reused by images with same size. + * if the cache does not exist yet it will be created and populated with new calculations. + * By default it is true. + */ + fun decode(blurHash: String?, width: Int, height: Int, punch: Float = 1f, useCache: Boolean = true): Bitmap? { + if (blurHash == null || blurHash.length < 6) { + return null + } + val numCompEnc = decode83(blurHash, 0, 1) + val numCompX = (numCompEnc % 9) + 1 + val numCompY = (numCompEnc / 9) + 1 + if (blurHash.length != 4 + 2 * numCompX * numCompY) { + return null + } + val maxAcEnc = decode83(blurHash, 1, 2) + val maxAc = (maxAcEnc + 1) / 166f + val colors = Array(numCompX * numCompY) { i -> + if (i == 0) { + val colorEnc = decode83(blurHash, 2, 6) + decodeDc(colorEnc) + } else { + val from = 4 + i * 2 + val colorEnc = decode83(blurHash, from, from + 2) + decodeAc(colorEnc, maxAc * punch) + } + } + return composeBitmap(width, height, numCompX, numCompY, colors, useCache) + } + + private fun decode83(str: String, from: Int = 0, to: Int = str.length): Int { + var result = 0 + for (i in from until to) { + val index = charMap[str[i]] ?: -1 + if (index != -1) { + result = result * 83 + index + } + } + return result + } + + private fun decodeDc(colorEnc: Int): FloatArray { + val r = colorEnc shr 16 + val g = (colorEnc shr 8) and 255 + val b = colorEnc and 255 + return floatArrayOf(BlurhashHelpers.srgbToLinear(r), BlurhashHelpers.srgbToLinear(g), BlurhashHelpers.srgbToLinear(b)) + } + + private fun decodeAc(value: Int, maxAc: Float): FloatArray { + val r = value / (19 * 19) + val g = (value / 19) % 19 + val b = value % 19 + return floatArrayOf( + signedPow2((r - 9) / 9.0f) * maxAc, + signedPow2((g - 9) / 9.0f) * maxAc, + signedPow2((b - 9) / 9.0f) * maxAc + ) + } + + private fun signedPow2(value: Float) = value.pow(2f).withSign(value) + + private fun composeBitmap( + width: Int, + height: Int, + numCompX: Int, + numCompY: Int, + colors: Array, + useCache: Boolean + ): Bitmap { + // use an array for better performance when writing pixel colors + val imageArray = IntArray(width * height) + val calculateCosX = !useCache || !cacheCosinesX.containsKey(width * numCompX) + val cosinesX = getArrayForCosinesX(calculateCosX, width, numCompX) + val calculateCosY = !useCache || !cacheCosinesY.containsKey(height * numCompY) + val cosinesY = getArrayForCosinesY(calculateCosY, height, numCompY) + for (y in 0 until height) { + for (x in 0 until width) { + var r = 0f + var g = 0f + var b = 0f + for (j in 0 until numCompY) { + for (i in 0 until numCompX) { + val cosX = cosinesX.getCos(calculateCosX, i, numCompX, x, width) + val cosY = cosinesY.getCos(calculateCosY, j, numCompY, y, height) + val basis = (cosX * cosY).toFloat() + val color = colors[j * numCompX + i] + r += color[0] * basis + g += color[1] * basis + b += color[2] * basis + } + } + imageArray[x + width * y] = Color.rgb(linearToSrgb(r), linearToSrgb(g), linearToSrgb(b)) + } + } + return Bitmap.createBitmap(imageArray, width, height, Bitmap.Config.ARGB_8888) + } + + private fun getArrayForCosinesY(calculate: Boolean, height: Int, numCompY: Int) = when { + calculate -> { + DoubleArray(height * numCompY).also { + cacheCosinesY[height * numCompY] = it + } + } + else -> { + cacheCosinesY[height * numCompY]!! + } + } + + private fun getArrayForCosinesX(calculate: Boolean, width: Int, numCompX: Int) = when { + calculate -> { + DoubleArray(width * numCompX).also { + cacheCosinesX[width * numCompX] = it + } + } + else -> cacheCosinesX[width * numCompX]!! + } + + private fun DoubleArray.getCos( + calculate: Boolean, + x: Int, + numComp: Int, + y: Int, + size: Int + ): Double { + if (calculate) { + this[x + numComp * y] = cos(Math.PI * y * x / size) + } + return this[x + numComp * y] + } + + private fun linearToSrgb(value: Float): Int { + val v = value.coerceIn(0f, 1f) + return if (v <= 0.0031308f) { + (v * 12.92f * 255f + 0.5f).toInt() + } else { + ((1.055f * v.pow(1 / 2.4f) - 0.055f) * 255 + 0.5f).toInt() + } + } + + private val charMap = listOf( + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '#', '$', '%', '*', '+', ',', + '-', '.', ':', ';', '=', '?', '@', '[', ']', '^', '_', '{', '|', '}', '~' + ) + .mapIndexed { i, c -> c to i } + .toMap() +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashEncoder.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashEncoder.kt new file mode 100644 index 00000000..f0a7e655 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashEncoder.kt @@ -0,0 +1,112 @@ +package expo.modules.image.blurhash + +import android.graphics.Bitmap +import android.graphics.Color +import kotlin.math.* + +/** + * Rewritten in kotlin from https://github.com/woltapp/blurhash/blob/master/Swift/BlurHashEncode.swift + */ +object BlurhashEncoder { + fun encode(image: Bitmap, numberOfComponents: Pair): String { + val pixels = IntArray(image.width * image.height) + image.getPixels(pixels, 0, image.width, 0, 0, image.width, image.height) + + val factors = calculateBlurFactors(pixels, image.width, image.height, numberOfComponents) + + val dc = factors.first() + val ac = factors.drop(1) + val hashBuilder = StringBuilder() + + encodeFlag(numberOfComponents, hashBuilder) + val maximumValue = encodeMaximumValue(ac, hashBuilder) + hashBuilder.append(encode83(encodeDC(dc), 4)) + for (factor in ac) { + hashBuilder.append(encode83(encodeAC(factor, maximumValue), 2)) + } + + return hashBuilder.toString() + } + + private fun encodeFlag(numberOfComponents: Pair, hashBuilder: StringBuilder) { + val sizeFlag = (numberOfComponents.first - 1) + (numberOfComponents.second - 1) * 9 + hashBuilder.append(encode83(sizeFlag, 1)) + } + + private fun encodeMaximumValue(ac: List>, hash: StringBuilder): Float { + val maximumValue: Float + if (ac.isNotEmpty()) { + val actualMaximumValue = ac.maxOf { t -> max(max(abs(t.first), abs(t.second)), abs(t.third)) } + val quantisedMaximumValue = max(0f, min(82f, floor(actualMaximumValue * 166f - 0.5f))).toInt() + maximumValue = (quantisedMaximumValue + 1).toFloat() / 166f + hash.append(encode83(quantisedMaximumValue, 1)) + } else { + maximumValue = 1f + hash.append(encode83(0, 1)) + } + return maximumValue + } + + private fun calculateBlurFactors(pixels: IntArray, width: Int, height: Int, numberOfComponents: Pair): List> { + val factors = mutableListOf>() + for (y in 0 until numberOfComponents.second) { + for (x in 0 until numberOfComponents.first) { + val normalisation = if (x == 0 && y == 0) 1f else 2f + val factor = multiplyBasisFunction(pixels, width, height, x, y, normalisation) + factors.add(factor) + } + } + return factors + } + + private fun encode83(value: Int, length: Int): String { + var result = "" + for (i in 1..length) { + val digit = (value / 83f.pow((length - i).toFloat())) % 83f + result += ENCODE_CHARACTERS[digit.toInt()] + } + return result + } + + private const val ENCODE_CHARACTERS = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~" + + private fun encodeDC(value: Triple): Int { + val roundedR = BlurhashHelpers.linearTosRGB(value.first) + val roundedG = BlurhashHelpers.linearTosRGB(value.second) + val roundedB = BlurhashHelpers.linearTosRGB(value.third) + return (roundedR shl 16) + (roundedG shl 8) + roundedB + } + + private fun encodeAC(value: Triple, maximumValue: Float): Int { + val quantR = max(0f, min(18f, floor(BlurhashHelpers.signPow(value.first / maximumValue, 0.5f) * 9f + 9.5f))) + val quantG = max(0f, min(18f, floor(BlurhashHelpers.signPow(value.second / maximumValue, 0.5f) * 9f + 9.5f))) + val quantB = max(0f, min(18f, floor(BlurhashHelpers.signPow(value.third / maximumValue, 0.5f) * 9f + 9.5f))) + + return (quantR * 19f * 19f + quantG * 19f + quantB).toInt() + } + + private fun multiplyBasisFunction(pixels: IntArray, width: Int, height: Int, x: Int, y: Int, normalisation: Float): Triple { + var r = 0f + var g = 0f + var b = 0f + + for (j in 0 until height) { + for (i in 0 until width) { + val basis = normalisation * cos(PI.toFloat() * x * i / width) * cos(PI.toFloat() * y * j / height) + + val pixel = pixels[i + j * width] + val pr = BlurhashHelpers.srgbToLinear(Color.red(pixel)) + val pg = BlurhashHelpers.srgbToLinear(Color.green(pixel)) + val pb = BlurhashHelpers.srgbToLinear(Color.blue(pixel)) + + r += basis * pr + g += basis * pg + b += basis * pb + } + } + + val scale = 1f / (width * height) + return Triple(r * scale, g * scale, b * scale) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashHelpers.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashHelpers.kt new file mode 100644 index 00000000..1c648a19 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashHelpers.kt @@ -0,0 +1,38 @@ +package expo.modules.image.blurhash + +import android.graphics.Bitmap +import kotlin.math.* + +object BlurhashHelpers { + fun srgbToLinear(colorEnc: Int): Float { + val v = colorEnc / 255f + return if (v <= 0.04045f) { + (v / 12.92f) + } else { + ((v + 0.055f) / 1.055f).pow(2.4f) + } + } + + fun linearTosRGB(value: Float): Int { + val v = max(0f, min(1f, value)) + return if (v <= 0.0031308) { + (v * 12.92 * 255 + 0.5).toInt() + } else { + (1.055 * (v.pow(1f / 2.4f) - 0.055) * 255 + 0.5).toInt() + } + } + + fun signPow(value: Float, exp: Float): Float { + return abs(value).pow(exp) * sign(value) + } + + fun getBitsPerPixel(bitmap: Bitmap): Int { + return when (bitmap.config) { + Bitmap.Config.ARGB_8888 -> 32 + Bitmap.Config.RGB_565 -> 16 + Bitmap.Config.ALPHA_8 -> 8 + Bitmap.Config.ARGB_4444 -> 16 + else -> 0 + } + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModel.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModel.kt new file mode 100644 index 00000000..0aecd7f4 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModel.kt @@ -0,0 +1,9 @@ +package expo.modules.image.blurhash + +import android.net.Uri + +data class BlurhashModel( + val uri: Uri, + val width: Int, + val height: Int +) diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoader.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoader.kt new file mode 100644 index 00000000..292d65bf --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoader.kt @@ -0,0 +1,29 @@ +package expo.modules.image.blurhash + +import android.graphics.Bitmap +import android.net.Uri +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.signature.ObjectKey + +class BlurhashModelLoader : ModelLoader { + override fun handles(model: BlurhashModel): Boolean = true + + override fun buildLoadData( + model: BlurhashModel, + width: Int, + height: Int, + options: Options + ): ModelLoader.LoadData { + val blurhash = getPath(model.uri, 0, null) { it } + return ModelLoader.LoadData( + ObjectKey(model), + BlurHashFetcher(blurhash, model.width, model.height, 1f) + ) + } + + private fun getPath(uri: Uri, index: Int, default: T, converter: (String) -> T): T { + val value = uri.pathSegments.getOrNull(index) ?: return default + return converter(value) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoaderFactory.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoaderFactory.kt new file mode 100644 index 00000000..438ecdba --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModelLoaderFactory.kt @@ -0,0 +1,13 @@ +package expo.modules.image.blurhash + +import android.graphics.Bitmap +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.load.model.ModelLoaderFactory +import com.bumptech.glide.load.model.MultiModelLoaderFactory + +class BlurhashModelLoaderFactory : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader = + BlurhashModelLoader() + + override fun teardown() = Unit +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModule.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModule.kt new file mode 100644 index 00000000..980ef352 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/blurhash/BlurhashModule.kt @@ -0,0 +1,16 @@ +package expo.modules.image.blurhash + +import android.content.Context +import android.graphics.Bitmap +import com.bumptech.glide.Glide +import com.bumptech.glide.Registry +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.module.LibraryGlideModule + +@GlideModule +class BlurhashModule : LibraryGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + super.registerComponents(context, glide, registry) + registry.prepend(BlurhashModel::class.java, Bitmap::class.java, BlurhashModelLoaderFactory()) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64DataFetcher.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64DataFetcher.kt new file mode 100644 index 00000000..55e33c26 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64DataFetcher.kt @@ -0,0 +1,27 @@ +package expo.modules.image.dataurls + +import android.util.Base64 +import com.bumptech.glide.Priority +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.data.DataFetcher +import java.nio.ByteBuffer + +class Base64DataFetcher(private val data: String) : DataFetcher { + override fun cleanup() = Unit + override fun cancel() = Unit + override fun getDataClass(): Class = ByteBuffer::class.java + override fun getDataSource(): DataSource = DataSource.LOCAL + + override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + val base64Section = getBase64Section() + val data = Base64.decode(base64Section, Base64.DEFAULT) + val byteBuffer = ByteBuffer.wrap(data) + callback.onDataReady(byteBuffer) + } + + private fun getBase64Section(): String { + // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs. + val startOfBase64Section = data.indexOf(',') + return data.substring(startOfBase64Section + 1) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoader.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoader.kt new file mode 100644 index 00000000..9445b749 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoader.kt @@ -0,0 +1,24 @@ +package expo.modules.image.dataurls + +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.signature.ObjectKey +import java.nio.ByteBuffer + +/** + * Loads an [java.io.InputStream] from a Base 64 encoded String. + */ +class Base64ModelLoader : ModelLoader { + override fun handles(model: String): Boolean { + return model.startsWith("data:") + } + + override fun buildLoadData( + model: String, + width: Int, + height: Int, + options: Options + ): ModelLoader.LoadData { + return ModelLoader.LoadData(ObjectKey(model), Base64DataFetcher(model)) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoaderFactory.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoaderFactory.kt new file mode 100644 index 00000000..fc7f62fd --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64ModelLoaderFactory.kt @@ -0,0 +1,12 @@ +package expo.modules.image.dataurls + +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.load.model.ModelLoaderFactory +import com.bumptech.glide.load.model.MultiModelLoaderFactory +import java.nio.ByteBuffer + +class Base64ModelLoaderFactory : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader = + Base64ModelLoader() + override fun teardown() = Unit +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64Module.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64Module.kt new file mode 100644 index 00000000..d363af17 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/dataurls/Base64Module.kt @@ -0,0 +1,16 @@ +package expo.modules.image.dataurls + +import android.content.Context +import com.bumptech.glide.Glide +import com.bumptech.glide.Registry +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.module.LibraryGlideModule +import java.nio.ByteBuffer + +@GlideModule +class Base64Module : LibraryGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + super.registerComponents(context, glide, registry) + registry.prepend(String::class.java, ByteBuffer::class.java, Base64ModelLoaderFactory()) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedFetcher.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedFetcher.kt new file mode 100644 index 00000000..dc408851 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedFetcher.kt @@ -0,0 +1,19 @@ +package expo.modules.image.decodedsource + +import android.graphics.drawable.Drawable +import com.bumptech.glide.Priority +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.data.DataFetcher + +class DecodedFetcher( + private val drawable: Drawable +) : DataFetcher { + override fun cleanup() = Unit + override fun cancel() = Unit + override fun getDataClass(): Class = Drawable::class.java + override fun getDataSource(): DataSource = DataSource.LOCAL + + override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + callback.onDataReady(drawable) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModel.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModel.kt new file mode 100644 index 00000000..8fa9a8af --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModel.kt @@ -0,0 +1,5 @@ +package expo.modules.image.decodedsource + +import android.graphics.drawable.Drawable + +class DecodedModel(val drawable: Drawable) diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModelLoader.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModelLoader.kt new file mode 100644 index 00000000..d8e5a8fd --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModelLoader.kt @@ -0,0 +1,18 @@ +package expo.modules.image.decodedsource + +import android.graphics.drawable.Drawable +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.signature.ObjectKey + +class DecodedModelLoader : ModelLoader { + override fun handles(model: DecodedModel): Boolean = true + override fun buildLoadData( + model: DecodedModel, + width: Int, + height: Int, + options: Options + ): ModelLoader.LoadData { + return ModelLoader.LoadData(ObjectKey(model), DecodedFetcher(model.drawable)) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModelLoaderFactory.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModelLoaderFactory.kt new file mode 100644 index 00000000..fe74cc9f --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModelLoaderFactory.kt @@ -0,0 +1,13 @@ +package expo.modules.image.decodedsource + +import android.graphics.drawable.Drawable +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.load.model.ModelLoaderFactory +import com.bumptech.glide.load.model.MultiModelLoaderFactory + +class DecodedModelLoaderFactory : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader = + DecodedModelLoader() + + override fun teardown() = Unit +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModule.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModule.kt new file mode 100644 index 00000000..eb22a0f9 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/decodedsource/DecodedModule.kt @@ -0,0 +1,16 @@ +package expo.modules.image.decodedsource + +import android.content.Context +import android.graphics.drawable.Drawable +import com.bumptech.glide.Glide +import com.bumptech.glide.Registry +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.module.LibraryGlideModule + +@GlideModule +class DecodedModule : LibraryGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + super.registerComponents(context, glide, registry) + registry.prepend(DecodedModel::class.java, Drawable::class.java, DecodedModelLoaderFactory()) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/drawing/OutlineProvider.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/drawing/OutlineProvider.kt new file mode 100644 index 00000000..5178d03b --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/drawing/OutlineProvider.kt @@ -0,0 +1,218 @@ +package expo.modules.image.drawing + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Outline +import android.graphics.Path +import android.graphics.RectF +import android.os.Build +import android.view.View +import android.view.ViewOutlineProvider +import com.facebook.react.modules.i18nmanager.I18nUtil +import com.facebook.react.uimanager.FloatUtil +import com.facebook.react.uimanager.PixelUtil +import com.facebook.yoga.YogaConstants +import expo.modules.image.ifYogaUndefinedUse + +class OutlineProvider(private val mContext: Context) : ViewOutlineProvider() { + enum class BorderRadiusConfig { + ALL, + TOP_LEFT, + TOP_RIGHT, + BOTTOM_RIGHT, + BOTTOM_LEFT, + TOP_START, + TOP_END, + BOTTOM_START, + BOTTOM_END + } + + enum class CornerRadius { + TOP_LEFT, + TOP_RIGHT, + BOTTOM_RIGHT, + BOTTOM_LEFT + } + + private var mLayoutDirection = View.LAYOUT_DIRECTION_LTR + private val mBounds = RectF() + val borderRadiiConfig = FloatArray(9) { YogaConstants.UNDEFINED } + private val mCornerRadii = FloatArray(4) + private var mCornerRadiiInvalidated = true + private val mConvexPath = Path() + private var mConvexPathInvalidated = true + + init { + updateCornerRadiiIfNeeded() + } + + private fun updateCornerRadiiIfNeeded() { + if (!mCornerRadiiInvalidated) { + return + } + + val isRTL = mLayoutDirection == View.LAYOUT_DIRECTION_RTL + val isRTLSwap = I18nUtil.instance.doLeftAndRightSwapInRTL(mContext) + updateCornerRadius( + CornerRadius.TOP_LEFT, + BorderRadiusConfig.TOP_LEFT, + BorderRadiusConfig.TOP_RIGHT, + BorderRadiusConfig.TOP_START, + BorderRadiusConfig.TOP_END, + isRTL, + isRTLSwap + ) + updateCornerRadius( + CornerRadius.TOP_RIGHT, + BorderRadiusConfig.TOP_RIGHT, + BorderRadiusConfig.TOP_LEFT, + BorderRadiusConfig.TOP_END, + BorderRadiusConfig.TOP_START, + isRTL, + isRTLSwap + ) + updateCornerRadius( + CornerRadius.BOTTOM_LEFT, + BorderRadiusConfig.BOTTOM_LEFT, + BorderRadiusConfig.BOTTOM_RIGHT, + BorderRadiusConfig.BOTTOM_START, + BorderRadiusConfig.BOTTOM_END, + isRTL, + isRTLSwap + ) + updateCornerRadius( + CornerRadius.BOTTOM_RIGHT, + BorderRadiusConfig.BOTTOM_RIGHT, + BorderRadiusConfig.BOTTOM_LEFT, + BorderRadiusConfig.BOTTOM_END, + BorderRadiusConfig.BOTTOM_START, + isRTL, + isRTLSwap + ) + mCornerRadiiInvalidated = false + mConvexPathInvalidated = true + } + + private fun updateCornerRadius( + outputPosition: CornerRadius, + inputPosition: BorderRadiusConfig, + oppositePosition: BorderRadiusConfig, + startPosition: BorderRadiusConfig, + endPosition: BorderRadiusConfig, + isRTL: Boolean, + isRTLSwap: Boolean + ) { + var radius = borderRadiiConfig[inputPosition.ordinal] + if (isRTL) { + if (isRTLSwap) { + radius = borderRadiiConfig[oppositePosition.ordinal] + } + if (YogaConstants.isUndefined(radius)) { + radius = borderRadiiConfig[endPosition.ordinal] + } + } else { + if (YogaConstants.isUndefined(radius)) { + radius = borderRadiiConfig[startPosition.ordinal] + } + } + radius = radius + .ifYogaUndefinedUse(borderRadiiConfig[BorderRadiusConfig.ALL.ordinal]) + .ifYogaUndefinedUse(0f) + mCornerRadii[outputPosition.ordinal] = PixelUtil.toPixelFromDIP(radius) + } + + private fun updateConvexPathIfNeeded() { + if (!mConvexPathInvalidated) { + return + } + mConvexPath.reset() + mConvexPath.addRoundRect( + mBounds, + floatArrayOf( + mCornerRadii[CornerRadius.TOP_LEFT.ordinal], + mCornerRadii[CornerRadius.TOP_LEFT.ordinal], + mCornerRadii[CornerRadius.TOP_RIGHT.ordinal], + mCornerRadii[CornerRadius.TOP_RIGHT.ordinal], + mCornerRadii[CornerRadius.BOTTOM_RIGHT.ordinal], + mCornerRadii[CornerRadius.BOTTOM_RIGHT.ordinal], + mCornerRadii[CornerRadius.BOTTOM_LEFT.ordinal], + mCornerRadii[CornerRadius.BOTTOM_LEFT.ordinal] + ), + Path.Direction.CW + ) + mConvexPathInvalidated = false + } + + fun hasEqualCorners(): Boolean { + updateCornerRadiiIfNeeded() + val initialCornerRadius = mCornerRadii[0] + return mCornerRadii.all { initialCornerRadius == it } + } + + fun setBorderRadius(radius: Float, position: Int): Boolean { + if (!FloatUtil.floatsEqual(borderRadiiConfig[position], radius)) { + borderRadiiConfig[position] = radius + mCornerRadiiInvalidated = true + return true + } + return false + } + + private fun updateBoundsAndLayoutDirection(view: View) { + // Update layout direction + val layoutDirection = view.layoutDirection + if (mLayoutDirection != layoutDirection) { + mLayoutDirection = layoutDirection + mCornerRadiiInvalidated = true + } + + // Update size + val left = 0 + val top = 0 + val right = view.width + val bottom = view.height + if (mBounds.left != left.toFloat() || + mBounds.top != top.toFloat() || + mBounds.right != right.toFloat() || + mBounds.bottom != bottom.toFloat() + ) { + mBounds[left.toFloat(), top.toFloat(), right.toFloat()] = bottom.toFloat() + mCornerRadiiInvalidated = true + } + } + + override fun getOutline(view: View, outline: Outline) { + updateBoundsAndLayoutDirection(view) + + // Calculate outline + updateCornerRadiiIfNeeded() + if (hasEqualCorners()) { + val cornerRadius = mCornerRadii[0] + if (cornerRadius > 0) { + outline.setRoundRect(0, 0, mBounds.width().toInt(), mBounds.height().toInt(), cornerRadius) + } else { + outline.setRect(0, 0, mBounds.width().toInt(), mBounds.height().toInt()) + } + } else { + // Clipping is not supported when using a convex path, but drawing the elevation + // shadow is. For the particular case, we fallback to canvas clipping in the view + // which is supposed to call `clipCanvasIfNeeded` in its `draw` method. + updateConvexPathIfNeeded() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + outline.setPath(mConvexPath) + } else { + @Suppress("DEPRECATION") + outline.setConvexPath(mConvexPath) + } + } + } + + fun clipCanvasIfNeeded(canvas: Canvas, view: View) { + updateBoundsAndLayoutDirection(view) + updateCornerRadiiIfNeeded() + if (!hasEqualCorners()) { + updateConvexPathIfNeeded() + canvas.clipPath(mConvexPath) + } + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/enums/ContentFit.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/enums/ContentFit.kt new file mode 100644 index 00000000..0519a020 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/enums/ContentFit.kt @@ -0,0 +1,89 @@ +package expo.modules.image.enums + +import android.graphics.Matrix +import android.graphics.RectF +import expo.modules.kotlin.types.Enumerable +import kotlin.math.max + +/** + * Describes how the image should be resized to fit its container. + * - Note: It mirrors the CSS [`object-fit`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) property. + */ +enum class ContentFit(val value: String) : Enumerable { + /** + * The image is scaled to maintain its aspect ratio while fitting within the container's box. + * The entire image is made to fill the box, while preserving its aspect ratio, + * so the image will be "letterboxed" if its aspect ratio does not match the aspect ratio of the box. + */ + Contain("contain"), + + /** + * The image is sized to maintain its aspect ratio while filling the element's entire content box. + * If the image's aspect ratio does not match the aspect ratio of its box, then the object will be clipped to fit. + */ + Cover("cover"), + + /** + * The image is sized to fill the element's content box. The entire object will completely fill the box. + * If the image's aspect ratio does not match the aspect ratio of its box, then the image will be stretched to fit. + */ + Fill("fill"), + + /** + * The image is not resized and is centered by default. + * When specified, the exact position can be controlled with `ContentPosition`. + */ + None("none"), + + /** + * The image is sized as if `none` or `contain` were specified, + * whichever would result in a smaller concrete image size. + */ + ScaleDown("scale-down"); + + internal fun toMatrix(imageRect: RectF, viewRect: RectF, sourceWidth: Int, sourceHeight: Int) = Matrix().apply { + when (this@ContentFit) { + Contain -> setRectToRect(imageRect, viewRect, Matrix.ScaleToFit.START) + Cover -> { + val imageWidth = imageRect.width() + val imageHeight = imageRect.height() + + val reactWidth = viewRect.width() + val reactHeight = viewRect.height() + + val scale = max(reactWidth / imageWidth, reactHeight / imageHeight) + + setScale(scale, scale) + } + Fill -> setRectToRect(imageRect, viewRect, Matrix.ScaleToFit.FILL) + None -> { + // we don't need to do anything + } + ScaleDown -> { + // If we have information about the original size of the source, we can resize the image more drastically. + // In certain situations, we may even permit upscaling when we anticipate the image to be reloaded without any reduction in size, + // which will create a seamless transition between various states. + if (sourceWidth != -1 && sourceHeight != -1) { + val sourceRect = RectF(0f, 0f, sourceWidth.toFloat(), sourceHeight.toFloat()) + // Rather than checking if the image rectangle is within the bounds of the view rectangle, we verify the original source rectangle. + // We know that the newly loaded image has larger dimensions than the current one, and therefore, + // it will not be downscaled. + if (sourceRect.width() >= viewRect.width() || sourceRect.height() >= viewRect.height()) { + setRectToRect(imageRect, viewRect, Matrix.ScaleToFit.START) + } + // If the source rectangle is larger than the view rectangle and the image rectangle has not been upscaled to match the source rectangle, + // temporary upscaling is necessary to ensure a seamless transition. + // It should be noted that this upscaling is applied to the downscaled version of the image, + // not the original source image, and will be replaced by the original asset shortly thereafter. + else { + setRectToRect(imageRect, sourceRect, Matrix.ScaleToFit.START) + } + } else { + if (imageRect.width() >= viewRect.width() || imageRect.height() >= viewRect.height()) { + setRectToRect(imageRect, viewRect, Matrix.ScaleToFit.START) + } + } + } + } + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/enums/ImageCacheType.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/enums/ImageCacheType.kt new file mode 100644 index 00000000..3f814fe8 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/enums/ImageCacheType.kt @@ -0,0 +1,14 @@ +package expo.modules.image.enums + +import com.bumptech.glide.load.DataSource + +enum class ImageCacheType(private vararg val dataSources: DataSource) { + NONE(DataSource.LOCAL, DataSource.REMOTE), + DISK(DataSource.DATA_DISK_CACHE, DataSource.RESOURCE_DISK_CACHE), + MEMORY(DataSource.MEMORY_CACHE); + + companion object { + fun fromNativeValue(value: DataSource): ImageCacheType = + entries.firstOrNull { it.dataSources.contains(value) } ?: NONE + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/enums/Priority.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/enums/Priority.kt new file mode 100644 index 00000000..87393a2c --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/enums/Priority.kt @@ -0,0 +1,15 @@ +package expo.modules.image.enums + +import expo.modules.kotlin.types.Enumerable + +enum class Priority(val value: String) : Enumerable { + LOW("low"), + NORMAL("normal"), + HIGH("high"); + + internal fun toGlidePriority(): com.bumptech.glide.Priority = when (this) { + LOW -> com.bumptech.glide.Priority.LOW + NORMAL -> com.bumptech.glide.Priority.NORMAL + HIGH -> com.bumptech.glide.Priority.IMMEDIATE + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/events/GlideRequestListener.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/events/GlideRequestListener.kt new file mode 100644 index 00000000..b8cccb5e --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/events/GlideRequestListener.kt @@ -0,0 +1,77 @@ +package expo.modules.image.events + +import android.graphics.drawable.Animatable +import android.graphics.drawable.Drawable +import android.util.Log +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target +import expo.modules.image.ExpoImageViewWrapper +import expo.modules.image.enums.ImageCacheType +import expo.modules.image.records.ImageErrorEvent +import expo.modules.image.records.ImageLoadEvent +import expo.modules.image.records.ImageSource +import expo.modules.image.svg.SVGPictureDrawable +import kotlinx.coroutines.launch +import java.lang.ref.WeakReference +import java.util.Locale + +class GlideRequestListener( + private val expoImageViewWrapper: WeakReference +) : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target, + isFirstResource: Boolean + ): Boolean { + val errorMessage = e + ?.message + // Glide always append that line to the end of the message. + // It's not possible to call `logRootCauses` from the JS, so we decided to remove it. + ?.removeSuffix("\n call GlideException#logRootCauses(String) for more detail") + ?: "Unknown error" + + expoImageViewWrapper + .get() + ?.onError + ?.invoke(ImageErrorEvent(errorMessage)) + + Log.e("ExpoImage", errorMessage) + e?.logRootCauses("ExpoImage") + return false + } + + override fun onResourceReady( + resource: Drawable, + model: Any, + target: Target, + dataSource: DataSource, + isFirstResource: Boolean + ): Boolean { + val intrinsicWidth = (resource as? SVGPictureDrawable)?.svgIntrinsicWidth + ?: resource.intrinsicWidth + val intrinsicHeight = (resource as? SVGPictureDrawable)?.svgIntrinsicHeight + ?: resource.intrinsicHeight + + val imageWrapper = expoImageViewWrapper.get() ?: return false + val appContext = imageWrapper.appContext + appContext.mainQueue.launch { + imageWrapper.onLoad.invoke( + ImageLoadEvent( + cacheType = ImageCacheType.fromNativeValue(dataSource).name.lowercase(Locale.getDefault()), + source = ImageSource( + url = model.toString(), + width = intrinsicWidth, + height = intrinsicHeight, + mediaType = null, // TODO(@lukmccall): add mediaType + isAnimated = resource is Animatable + ) + ) + ) + } + + return false + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/events/OkHttpProgressListener.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/events/OkHttpProgressListener.kt new file mode 100644 index 00000000..69c07614 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/events/OkHttpProgressListener.kt @@ -0,0 +1,26 @@ +package expo.modules.image.events + +import com.facebook.react.modules.network.ProgressListener +import expo.modules.image.ExpoImageViewWrapper +import expo.modules.image.records.ImageProgressEvent +import java.lang.ref.WeakReference + +class OkHttpProgressListener( + private val expoImageViewWrapper: WeakReference +) : ProgressListener { + override fun onProgress(bytesWritten: Long, contentLength: Long, done: Boolean) { + // OkHttp calls that function twice at the end - when the last byte was downloaded with done set to false, + // and also shortly after, with done set to true. In both cases, the bytesWritten and the contentLength are equal. + // We want to avoid sending two same events to JS, that's why we return when done is set to true. + if (contentLength <= 0 || done) { + return + } + + expoImageViewWrapper.get()?.onProgress?.invoke( + ImageProgressEvent( + loaded = bytesWritten.toInt(), + total = contentLength.toInt() + ) + ) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/okhttp/ExpoImageOkHttpClientGlideModule.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/okhttp/ExpoImageOkHttpClientGlideModule.kt new file mode 100644 index 00000000..071907c9 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/okhttp/ExpoImageOkHttpClientGlideModule.kt @@ -0,0 +1,80 @@ +package expo.modules.image.okhttp + +import android.content.Context +import com.bumptech.glide.Glide +import com.bumptech.glide.Registry +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader +import com.bumptech.glide.load.model.GlideUrl +import com.bumptech.glide.load.model.Headers +import com.bumptech.glide.module.LibraryGlideModule +import expo.modules.image.events.OkHttpProgressListener +import okhttp3.OkHttpClient +import java.io.InputStream + +/** + * GlideUrl with custom cache key. + * It wraps the base implementation and overrides logic behind cache key and when two + * objects are equal. Typically, Glide uses the only cache key to compare objects. + * It won't suit our use case. We want to make custom cache key transparent and + * not use it to compare objects. + */ +class GlideUrlWithCustomCacheKey( + uri: String?, + headers: Headers?, + private val cacheKey: String +) : GlideUrl(uri, headers) { + /** + * Cached hash code value + */ + private var hashCode = 0 + + /** + * @return a super cache key from [GlideUrl] + */ + private fun getBaseCacheKey(): String = super.getCacheKey() + + override fun getCacheKey(): String = cacheKey + + // Mostly copied from GlideUrl::equal + override fun equals(other: Any?): Boolean { + if (other is GlideUrlWithCustomCacheKey) { + return getBaseCacheKey() == other.getBaseCacheKey() && headers.equals(other.headers) + } else if (other is GlideUrl) { + return getBaseCacheKey() == other.cacheKey && headers.equals(other.headers) + } + return false + } + + // Mostly copied from GlideUrl::hashCode + override fun hashCode(): Int { + if (hashCode == 0) { + hashCode = getBaseCacheKey().hashCode() + hashCode = 31 * hashCode + headers.hashCode() + } + return hashCode + } +} + +/** + * To connect listener with the request we have to create custom model. + * In that way, we're passing more information to the final data loader. + */ +data class GlideUrlWrapper(val glideUrl: GlideUrl) { + var progressListener: OkHttpProgressListener? = null + + override fun toString(): String { + return glideUrl.toString() + } +} + +@GlideModule +class ExpoImageOkHttpClientGlideModule : LibraryGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + val client = OkHttpClient() + // We don't use the `GlideUrl` directly but we want to replace the default okhttp loader anyway + // to make sure that the app will use only one client. + registry.replace(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory(client)) + registry.prepend(GlideUrlWrapper::class.java, InputStream::class.java, GlideUrlWrapperLoader.Factory(client)) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/okhttp/GlideUrlWrapperLoader.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/okhttp/GlideUrlWrapperLoader.kt new file mode 100644 index 00000000..f0ffa8c0 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/okhttp/GlideUrlWrapperLoader.kt @@ -0,0 +1,55 @@ +package expo.modules.image.okhttp + +import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.load.model.ModelLoaderFactory +import com.bumptech.glide.load.model.MultiModelLoaderFactory +import com.facebook.react.modules.network.ProgressResponseBody +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import java.io.InputStream + +class GlideUrlWrapperLoader( + private val commonClient: OkHttpClient +) : ModelLoader { + override fun buildLoadData( + model: GlideUrlWrapper, + width: Int, + height: Int, + options: Options + ): ModelLoader.LoadData? { + val loader = OkHttpUrlLoader( + commonClient + .newBuilder() + .addInterceptor( + Interceptor { chain -> + val originalResponse = chain.proceed(chain.request()) + originalResponse + .newBuilder() + .body( + ProgressResponseBody(requireNotNull(originalResponse.body)) { bytesWritten, contentLength, done -> + model.progressListener?.onProgress(bytesWritten, contentLength, done) + } + ) + .build() + } + ) + .build() + ) + + return loader.buildLoadData(model.glideUrl, width, height, options) + } + + // The default http loader always returns true. + override fun handles(model: GlideUrlWrapper): Boolean = true + + class Factory( + private val commonClient: OkHttpClient + ) : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader = + GlideUrlWrapperLoader(commonClient) + + override fun teardown() = Unit + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/records/CachePolicy.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/CachePolicy.kt new file mode 100644 index 00000000..57ac9ed7 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/CachePolicy.kt @@ -0,0 +1,10 @@ +package expo.modules.image.records + +import expo.modules.kotlin.types.Enumerable + +enum class CachePolicy(val value: String) : Enumerable { + NONE("none"), + DISK("disk"), + MEMORY("memory"), + MEMORY_AND_DISK("memory-disk") +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/records/ContentPosition.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/ContentPosition.kt new file mode 100644 index 00000000..87ccf4d8 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/ContentPosition.kt @@ -0,0 +1,89 @@ +package expo.modules.image.records + +import android.graphics.Matrix +import android.graphics.RectF +import expo.modules.image.calcXTranslation +import expo.modules.image.calcYTranslation +import expo.modules.kotlin.records.Field +import expo.modules.kotlin.records.Record + +/** + * Represents a position value that might be either `Double` or `String`. + * TODO(@lukmccall): Use `Either` instead of `Any` + */ +typealias ContentPositionValue = Any + +private typealias CalcAxisOffset = ( + value: Float, + imageRect: RectF, + viewRect: RectF, + isPercentage: Boolean, + isReverse: Boolean +) -> Float + +class ContentPosition : Record { + @Field + val top: ContentPositionValue? = null + + @Field + val bottom: ContentPositionValue? = null + + @Field + val right: ContentPositionValue? = null + + @Field + val left: ContentPositionValue? = null + + private fun ContentPositionValue?.calcOffset( + isReverse: Boolean, + imageRect: RectF, + viewRect: RectF, + calcAxisOffset: CalcAxisOffset + ): Float? { + if (this == null) { + return null + } + + return if (this is Double) { + val value = this.toFloat() + calcAxisOffset(value, imageRect, viewRect, false, isReverse) + } else { + val value = this as String + + if (value == "center") { + calcAxisOffset(50f, imageRect, viewRect, true, isReverse) + } else { + calcAxisOffset(value.removeSuffix("%").toFloat(), imageRect, viewRect, true, isReverse) + } + } + } + + private fun offsetX( + imageRect: RectF, + viewRect: RectF + ): Float { + return left.calcOffset(false, imageRect, viewRect, ::calcXTranslation) + ?: right.calcOffset(true, imageRect, viewRect, ::calcXTranslation) + ?: calcXTranslation(50f, imageRect, viewRect, isPercentage = true) // default value + } + + private fun offsetY( + imageRect: RectF, + viewRect: RectF + ): Float { + return top.calcOffset(false, imageRect, viewRect, ::calcYTranslation) + ?: bottom.calcOffset(true, imageRect, viewRect, ::calcYTranslation) + ?: calcYTranslation(50f, imageRect, viewRect, isPercentage = true) // default value + } + + internal fun apply(to: Matrix, imageRect: RectF, viewRect: RectF) { + val xOffset = offsetX(imageRect, viewRect) + val yOffset = offsetY(imageRect, viewRect) + + to.postTranslate(xOffset, yOffset) + } + + companion object { + val center = ContentPosition() + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/records/DecodeFormat.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/DecodeFormat.kt new file mode 100644 index 00000000..373a8f8e --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/DecodeFormat.kt @@ -0,0 +1,22 @@ +package expo.modules.image.records + +import expo.modules.kotlin.types.Enumerable + +enum class DecodeFormat(val value: String) : Enumerable { + ARGB_8888("argb"), + RGB_565("rgb"); + + fun toGlideFormat(): com.bumptech.glide.load.DecodeFormat { + return when (this) { + ARGB_8888 -> com.bumptech.glide.load.DecodeFormat.PREFER_ARGB_8888 + RGB_565 -> com.bumptech.glide.load.DecodeFormat.PREFER_RGB_565 + } + } + + fun toBytes(): Int { + return when (this) { + ARGB_8888 -> 4 + RGB_565 -> 2 + } + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/records/ImageLoadOptions.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/ImageLoadOptions.kt new file mode 100644 index 00000000..52d095e8 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/ImageLoadOptions.kt @@ -0,0 +1,12 @@ +package expo.modules.image.records + +import com.bumptech.glide.request.target.Target.SIZE_ORIGINAL +import expo.modules.kotlin.records.Field +import expo.modules.kotlin.records.Record + +data class ImageLoadOptions( + @Field + val maxWidth: Int = SIZE_ORIGINAL, + @Field + val maxHeight: Int = SIZE_ORIGINAL +) : Record diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/records/ImageTransition.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/ImageTransition.kt new file mode 100644 index 00000000..08d9e654 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/ImageTransition.kt @@ -0,0 +1,8 @@ +package expo.modules.image.records + +import expo.modules.kotlin.records.Field +import expo.modules.kotlin.records.Record + +data class ImageTransition( + @Field val duration: Int = 0 +) : Record diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/records/SourceMap.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/SourceMap.kt new file mode 100644 index 00000000..72ad8fa9 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/SourceMap.kt @@ -0,0 +1,192 @@ +package expo.modules.image.records + +import android.content.Context +import android.graphics.drawable.Drawable +import android.net.Uri +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.model.GlideUrl +import com.bumptech.glide.load.model.Headers +import com.bumptech.glide.load.model.LazyHeaders +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.signature.ApplicationVersionSignature +import expo.modules.image.BlurhashModelProvider +import expo.modules.image.DecodedModelProvider +import expo.modules.image.GlideModelProvider +import expo.modules.image.RawModelProvider +import expo.modules.image.ThumbhashModelProvider +import expo.modules.image.UriModelProvider +import expo.modules.image.UrlModelProvider +import expo.modules.image.ResourceIdHelper +import expo.modules.image.customize +import expo.modules.image.okhttp.GlideUrlWithCustomCacheKey +import expo.modules.kotlin.records.Field +import expo.modules.kotlin.records.Record + +sealed interface Source { + val width: Int + val height: Int + val scale: Double + + val pixelCount: Double + get() = width * height * scale * scale + + fun createGlideModelProvider(context: Context): GlideModelProvider? + fun createGlideOptions(context: Context): RequestOptions + + /** + * Whether it should use placeholder content fit when used as a placeholder + */ + fun usesPlaceholderContentFit(): Boolean = true +} + +class DecodedSource( + val drawable: Drawable +) : Source { + override fun createGlideModelProvider(context: Context): GlideModelProvider { + return DecodedModelProvider(drawable) + } + + override val width: Int = drawable.intrinsicWidth + override val height: Int = drawable.intrinsicHeight + override val scale: Double = 1.0 + + override fun createGlideOptions(context: Context): RequestOptions { + // We don't want to cache already decoded images. + return RequestOptions() + .skipMemoryCache(true) + .diskCacheStrategy(DiskCacheStrategy.NONE) + } +} + +data class SourceMap( + @Field val uri: String? = null, + @Field override val width: Int = 0, + @Field override val height: Int = 0, + @Field override val scale: Double = 1.0, + @Field val headers: Map? = null, + @Field val cacheKey: String? = null +) : Source, Record { + private var parsedUri: Uri? = null + + private fun isDataUrl() = parsedUri?.scheme?.startsWith("data") ?: false + + private fun isContentUrl() = parsedUri?.scheme?.startsWith("content") ?: false + + private fun isResourceUri() = parsedUri?.scheme?.startsWith("android.resource") ?: false + + private fun isLocalResourceUri() = parsedUri?.scheme?.startsWith("res") ?: false + + private fun isLocalFileUri() = parsedUri?.scheme?.startsWith("file") ?: false + + private fun isBlurhash() = parsedUri?.scheme?.startsWith("blurhash") ?: false + + private fun isThumbhash() = parsedUri?.scheme?.startsWith("thumbhash") ?: false + + override fun usesPlaceholderContentFit(): Boolean { + return !isBlurhash() && !isThumbhash() + } + + private fun parseUri(context: Context) { + if (parsedUri == null) { + parsedUri = computeUri(context) + } + } + + override fun createGlideModelProvider(context: Context): GlideModelProvider? { + if (uri.isNullOrBlank()) { + return null + } + + parseUri(context) + if (isContentUrl() || isDataUrl()) { + return RawModelProvider(uri) + } + + if (isBlurhash()) { + return BlurhashModelProvider( + parsedUri!!, + width, + height + ) + } + + if (isThumbhash()) { + return ThumbhashModelProvider( + parsedUri!! + ) + } + + if (isResourceUri()) { + return UriModelProvider(parsedUri!!) + } + + if (isLocalResourceUri()) { + return UriModelProvider( + // Convert `res:/` scheme to `android.resource://`. + // Otherwise, glide can't understand the Uri. + Uri.parse(parsedUri!!.toString().replace("res:/", "android.resource://" + context.packageName + "/")) + ) + } + + if (isLocalFileUri()) { + return RawModelProvider(parsedUri!!.toString()) + } + + val glideUrl = if (cacheKey == null) { + GlideUrl(uri, getCustomHeaders()) + } else { + GlideUrlWithCustomCacheKey(uri, getCustomHeaders(), cacheKey) + } + return UrlModelProvider(glideUrl) + } + + override fun createGlideOptions(context: Context): RequestOptions { + parseUri(context) + return RequestOptions().customize(`when` = width != 0 && height != 0) { + // Override the size for local assets (apart from SVGs). This ensures that + // resizeMode "center" displays the image in the correct size. + override((width * scale).toInt(), (height * scale).toInt()) + }.customize(`when` = isResourceUri()) { + // Every local resource (drawable) in Android has its own unique numeric id, which are + // generated at build time. Although these ids are unique, they are not guaranteed unique + // across builds. The underlying glide implementation caches these resources. To make + // sure the cache does not return the wrong image, we should clear the cache when the + // application version changes. + apply(RequestOptions.signatureOf(ApplicationVersionSignature.obtain(context))) + } + } + + private fun getCustomHeaders(): Headers { + if (headers == null) { + return LazyHeaders.DEFAULT + } + + return LazyHeaders + .Builder() + .apply { + headers.forEach { (key, value) -> + addHeader(key, value) + } + } + .build() + } + + private fun computeUri(context: Context): Uri? { + val stringUri = uri ?: return null + return try { + val uri: Uri = Uri.parse(stringUri) + // Verify scheme is set, so that relative uri (used by static resources) are not handled. + if (uri.scheme == null) { + computeLocalUri(stringUri, context) + } else { + uri + } + } catch (e: Exception) { + computeLocalUri(stringUri, context) + } + } + + private fun computeLocalUri(stringUri: String, context: Context): Uri? { + return ResourceIdHelper.getResourceUri(context, stringUri) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/records/events.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/events.kt new file mode 100644 index 00000000..1ed89436 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/records/events.kt @@ -0,0 +1,26 @@ +package expo.modules.image.records + +import expo.modules.kotlin.records.Field +import expo.modules.kotlin.records.Record + +data class ImageSource( + @Field val url: String, + @Field val width: Int, + @Field val height: Int, + @Field val mediaType: String?, + @Field val isAnimated: Boolean +) : Record + +data class ImageLoadEvent( + @Field val cacheType: String, + @Field val source: ImageSource +) : Record + +data class ImageProgressEvent( + @Field val loaded: Int, + @Field val total: Int +) : Record + +data class ImageErrorEvent( + @Field val error: String +) : Record diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/svg/SVGDecoder.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/svg/SVGDecoder.kt new file mode 100644 index 00000000..2f459740 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/svg/SVGDecoder.kt @@ -0,0 +1,42 @@ +package expo.modules.image.svg + +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.ResourceDecoder +import com.bumptech.glide.load.engine.Resource +import com.bumptech.glide.load.resource.SimpleResource +import com.caverock.androidsvg.SVG +import com.caverock.androidsvg.SVGParseException +import java.io.IOException +import java.io.InputStream + +/** + * Decodes an SVG internal representation from an [InputStream]. + * + * Copied from https://github.com/bumptech/glide/blob/10acc31a16b4c1b5684f69e8de3117371dfa77a8/samples/svg/src/main/java/com/bumptech/glide/samples/svg/SvgDecoder.java + * and rewritten to Kotlin. + */ +class SVGDecoder : ResourceDecoder { + // TODO: Can we tell? + override fun handles(source: InputStream, options: Options) = true + + @Throws(IOException::class) + override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource? { + return try { + val svg: SVG = SVG.getFromInputStream(source) + // Use document width and height if view box is not set. + // Later, we will override the document width and height with the dimensions of the native view. + if (svg.documentViewBox == null) { + val documentWidth = svg.documentWidth + val documentHeight = svg.documentHeight + if (documentWidth != -1f && documentHeight != -1f) { + svg.setDocumentViewBox(0f, 0f, documentWidth, documentHeight) + } + } + svg.documentWidth = width.toFloat() + svg.documentHeight = height.toFloat() + SimpleResource(svg) + } catch (ex: SVGParseException) { + throw IOException("Cannot load SVG from stream", ex) + } + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/svg/SVGDrawableTranscoder.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/svg/SVGDrawableTranscoder.kt new file mode 100644 index 00000000..40c7fcec --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/svg/SVGDrawableTranscoder.kt @@ -0,0 +1,49 @@ +package expo.modules.image.svg + +import android.content.Context +import android.graphics.Picture +import android.graphics.drawable.Drawable +import android.graphics.drawable.PictureDrawable +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.engine.Resource +import com.bumptech.glide.load.resource.SimpleResource +import com.bumptech.glide.load.resource.transcode.ResourceTranscoder +import com.caverock.androidsvg.SVG +import com.caverock.androidsvg.applyTintColor +import expo.modules.image.CustomOptions + +/** + * We have to use the intrinsicWidth/Height from the Picture to render the image at a high enough resolution, but at the same time we want to return the actual + * preferred width and height of the SVG to JS. This class allows us to do that. + */ +class SVGPictureDrawable(picture: Picture, val svgIntrinsicWidth: Int, val svgIntrinsicHeight: Int) : PictureDrawable(picture) + +/** + * Convert the [SVG]'s internal representation to an Android-compatible one ([Picture]). + * + * Copied from https://github.com/bumptech/glide/blob/10acc31a16b4c1b5684f69e8de3117371dfa77a8/samples/svg/src/main/java/com/bumptech/glide/samples/svg/SvgDrawableTranscoder.java + * and rewritten to Kotlin. + */ +class SVGDrawableTranscoder(val context: Context) : ResourceTranscoder { + override fun transcode(toTranscode: Resource, options: Options): Resource { + val svgData = toTranscode.get() + // If the svg doesn't have a viewBox, we can't determine its intrinsic width and height, so we default to 512x512. + // Same dimensions are used in the AndroidSVG library when the viewBox is not set. + val intrinsicWidth = svgData.documentViewBox?.width()?.toInt() ?: 512 + val intrinsicHeight = svgData.documentViewBox?.height()?.toInt() ?: 512 + + val tintColor = options.get(CustomOptions.tintColor) + if (tintColor != null) { + applyTintColor(svgData, tintColor) + } + + val picture = SVGPictureDrawable( + svgData.renderToPicture(), + intrinsicWidth, + intrinsicHeight + ) + return SimpleResource( + picture + ) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/svg/SVGModule.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/svg/SVGModule.kt new file mode 100644 index 00000000..97970a36 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/svg/SVGModule.kt @@ -0,0 +1,26 @@ +package expo.modules.image.svg + +import android.content.Context +import android.graphics.drawable.Drawable +import com.bumptech.glide.Glide +import com.bumptech.glide.Registry +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.module.LibraryGlideModule +import com.caverock.androidsvg.SVG +import java.io.InputStream + +/** + * [LibraryGlideModule] registering support for SVG to Glide. + * + * Copied from https://github.com/bumptech/glide/blob/10acc31a16b4c1b5684f69e8de3117371dfa77a8/samples/svg/src/main/java/com/bumptech/glide/samples/svg/SvgModule.java + * and rewritten to Kotlin. + */ +@GlideModule +class SVGModule : LibraryGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + super.registerComponents(context, glide, registry) + registry + .append(InputStream::class.java, SVG::class.java, SVGDecoder()) + .register(SVG::class.java, Drawable::class.java, SVGDrawableTranscoder(context)) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashDecoder.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashDecoder.kt new file mode 100644 index 00000000..a5868cde --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashDecoder.kt @@ -0,0 +1,222 @@ +package expo.modules.image.thumbhash + +import android.graphics.Bitmap +import android.graphics.Color + +// ThumbHash Java implementation (converted to kotlin) thanks to @evanw https://github.com/evanw/thumbhash +object ThumbhashDecoder { + /** + * Decodes a ThumbHash to an RGBA image. RGB is not be premultiplied by A. + * + * @param hash The bytes of the ThumbHash. + * @return The width, height, and pixels of the rendered placeholder image. + */ + fun thumbHashToRGBA(hash: ByteArray): Image { + // Read the constants + val header24 = hash[0].toInt() and 255 or (hash[1].toInt() and 255 shl 8) or (hash[2].toInt() and 255 shl 16) + val header16 = hash[3].toInt() and 255 or (hash[4].toInt() and 255 shl 8) + val l_dc = (header24 and 63).toFloat() / 63.0f + val p_dc = (header24 shr 6 and 63).toFloat() / 31.5f - 1.0f + val q_dc = (header24 shr 12 and 63).toFloat() / 31.5f - 1.0f + val l_scale = (header24 shr 18 and 31).toFloat() / 31.0f + val hasAlpha = header24 shr 23 != 0 + val p_scale = (header16 shr 3 and 63).toFloat() / 63.0f + val q_scale = (header16 shr 9 and 63).toFloat() / 63.0f + val isLandscape = header16 shr 15 != 0 + val lx = Math.max(3, if (isLandscape) if (hasAlpha) 5 else 7 else header16 and 7) + val ly = Math.max(3, if (isLandscape) header16 and 7 else if (hasAlpha) 5 else 7) + val a_dc = if (hasAlpha) (hash[5].toInt() and 15).toFloat() / 15.0f else 1.0f + val a_scale = (hash[5].toInt() shr 4 and 15).toFloat() / 15.0f + + // Read the varying factors (boost saturation by 1.25x to compensate for quantization) + val ac_start = if (hasAlpha) 6 else 5 + var ac_index = 0 + val l_channel = Channel(lx, ly) + val p_channel = Channel(3, 3) + val q_channel = Channel(3, 3) + var a_channel: Channel? = null + ac_index = l_channel.decode(hash, ac_start, ac_index, l_scale) + ac_index = p_channel.decode(hash, ac_start, ac_index, p_scale * 1.25f) + ac_index = q_channel.decode(hash, ac_start, ac_index, q_scale * 1.25f) + if (hasAlpha) { + a_channel = Channel(5, 5) + a_channel.decode(hash, ac_start, ac_index, a_scale) + } + val l_ac = l_channel.ac + val p_ac = p_channel.ac + val q_ac = q_channel.ac + val a_ac = if (hasAlpha) a_channel!!.ac else null + + // Decode using the DCT into RGB + val ratio = thumbHashToApproximateAspectRatio(hash) + val w = Math.round(if (ratio > 1.0f) 32.0f else 32.0f * ratio) + val h = Math.round(if (ratio > 1.0f) 32.0f / ratio else 32.0f) + val rgba = ByteArray(w * h * 4) + val cx_stop = Math.max(lx, if (hasAlpha) 5 else 3) + val cy_stop = Math.max(ly, if (hasAlpha) 5 else 3) + val fx = FloatArray(cx_stop) + val fy = FloatArray(cy_stop) + var y = 0 + var i = 0 + while (y < h) { + var x = 0 + while (x < w) { + var l = l_dc + var p = p_dc + var q = q_dc + var a = a_dc + + // Precompute the coefficients + for (cx in 0 until cx_stop) fx[cx] = Math.cos(Math.PI / w * (x + 0.5f) * cx).toFloat() + for (cy in 0 until cy_stop) fy[cy] = Math.cos(Math.PI / h * (y + 0.5f) * cy).toFloat() + + // Decode L + run { + var cy = 0 + var j = 0 + while (cy < ly) { + val fy2 = fy[cy] * 2.0f + var cx = if (cy > 0) 0 else 1 + while (cx * ly < lx * (ly - cy)) { + l += l_ac[j] * fx[cx] * fy2 + cx++ + j++ + } + cy++ + } + } + + // Decode P and Q + var cy = 0 + var j = 0 + while (cy < 3) { + val fy2 = fy[cy] * 2.0f + var cx = if (cy > 0) 0 else 1 + while (cx < 3 - cy) { + val f = fx[cx] * fy2 + p += p_ac[j] * f + q += q_ac[j] * f + cx++ + j++ + } + cy++ + } + + // Decode A + if (hasAlpha) { + var cyAlpha = 0 + var k = 0 + while (cyAlpha < 5) { + val fy2 = fy[cyAlpha] * 2.0f + var cx = if (cyAlpha > 0) 0 else 1 + while (cx < 5 - cyAlpha) { + a += a_ac!![k] * fx[cx] * fy2 + cx++ + k++ + } + cyAlpha++ + } + } + + // Convert to RGB + val b = l - 2.0f / 3.0f * p + val r = (3.0f * l - b + q) / 2.0f + val g = r - q + rgba[i] = Math.max(0, Math.round(255.0f * Math.min(1f, r))).toByte() + rgba[i + 1] = Math.max(0, Math.round(255.0f * Math.min(1f, g))).toByte() + rgba[i + 2] = Math.max(0, Math.round(255.0f * Math.min(1f, b))).toByte() + rgba[i + 3] = Math.max(0, Math.round(255.0f * Math.min(1f, a))).toByte() + x++ + i += 4 + } + y++ + } + return Image(w, h, rgba) + } + + /** + * Converts a ThumbHash into a Bitmap image + */ + fun thumbHashToBitmap(hash: ByteArray): Bitmap { + val thumbhashImage = thumbHashToRGBA(hash) + // TODO: @behenate it should be possible to replace all of the code below with + // with BitmapFactory.decodeByteArray but it always returns null when using thumbhashImage.rgba + val imageArray = IntArray(thumbhashImage.width * thumbhashImage.height) + val thumbhashImageInt = thumbhashImage.rgba.map { it.toUByte().toInt() } + for (i in thumbhashImageInt.indices step 4) { + imageArray[i / 4] = Color.argb( + thumbhashImageInt[i + 3], + thumbhashImageInt[i], + thumbhashImageInt[i + 1], + thumbhashImageInt[i + 2] + ) + } + return Bitmap.createBitmap(imageArray, thumbhashImage.width, thumbhashImage.height, Bitmap.Config.ARGB_8888) + } + + /** + * Extracts the average color from a ThumbHash. RGB is not be premultiplied by A. + * + * @param hash The bytes of the ThumbHash. + * @return The RGBA values for the average color. Each value ranges from 0 to 1. + */ + fun thumbHashToAverageRGBA(hash: ByteArray): RGBA { + val header = hash[0].toInt() and 255 or (hash[1].toInt() and 255 shl 8) or (hash[2].toInt() and 255 shl 16) + val l = (header and 63).toFloat() / 63.0f + val p = (header shr 6 and 63).toFloat() / 31.5f - 1.0f + val q = (header shr 12 and 63).toFloat() / 31.5f - 1.0f + val hasAlpha = header shr 23 != 0 + val a = if (hasAlpha) (hash[5].toInt() and 15).toFloat() / 15.0f else 1.0f + val b = l - 2.0f / 3.0f * p + val r = (3.0f * l - b + q) / 2.0f + val g = r - q + return RGBA( + Math.max(0f, Math.min(1f, r)), + Math.max(0f, Math.min(1f, g)), + Math.max(0f, Math.min(1f, b)), + a + ) + } + + /** + * Extracts the approximate aspect ratio of the original image. + * + * @param hash The bytes of the ThumbHash. + * @return The approximate aspect ratio (i.e. width / height). + */ + fun thumbHashToApproximateAspectRatio(hash: ByteArray): Float { + val header = hash[3] + val hasAlpha = hash[2].toInt() and 0x80 != 0 + val isLandscape = hash[4].toInt() and 0x80 != 0 + val lx = if (isLandscape) if (hasAlpha) 5 else 7 else header.toInt() and 7 + val ly = if (isLandscape) header.toInt() and 7 else if (hasAlpha) 5 else 7 + return lx.toFloat() / ly.toFloat() + } + + class Image(var width: Int, var height: Int, var rgba: ByteArray) + class RGBA(var r: Float, var g: Float, var b: Float, var a: Float) + private class Channel(nx: Int, ny: Int) { + var ac: FloatArray + init { + var n = 0 + for (cy in 0 until ny) { + var cx = if (cy > 0) 0 else 1 + while (cx * ny < nx * (ny - cy)) { + n++ + cx++ + } + } + ac = FloatArray(n) + } + + fun decode(hash: ByteArray, start: Int, index: Int, scale: Float): Int { + var currentIndex = index + for (i in ac.indices) { + val data = hash[start + (currentIndex shr 1)].toInt() shr (currentIndex and 1 shl 2) + ac[i] = ((data and 15).toFloat() / 7.5f - 1.0f) * scale + currentIndex++ + } + return currentIndex + } + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashEncoder.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashEncoder.kt new file mode 100644 index 00000000..5008c283 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashEncoder.kt @@ -0,0 +1,184 @@ +package expo.modules.image.thumbhash + +import android.graphics.Bitmap +import android.graphics.Color + +// ThumbHash Java implementation (converted to kotlin) thanks to @evanw https://github.com/evanw/thumbhash +object ThumbhashEncoder { + /** + * Encodes an RGBA image to a ThumbHash. RGB should not be premultiplied by A. + * @param bitmap The bitmap to generate the ThumbHash from. + * @return The ThumbHash as a byte array. + */ + fun encode(bitmap: Bitmap): ByteArray { + // Encoding an image larger than 100x100 is slow with no benefit + val resizedBitmap = resizeKeepingAspectRatio(bitmap, 100) + val w = resizedBitmap.width + val h = resizedBitmap.height + + val pixels = IntArray(w * h) + resizedBitmap.getPixels(pixels, 0, w, 0, 0, w, h) + + var avg_r = 0f + var avg_g = 0f + var avg_b = 0f + var avg_a = 0f + var i = 0 + while (i < w * h) { + val alpha = Color.alpha(pixels[i]) / 255.0f + avg_r += alpha / 255.0f * Color.red(pixels[i]) + avg_g += alpha / 255.0f * Color.green(pixels[i]) + avg_b += alpha / 255.0f * Color.blue(pixels[i]) + avg_a += alpha + i++ + } + + if (avg_a > 0) { + avg_r /= avg_a + avg_g /= avg_a + avg_b /= avg_a + } + val hasAlpha = avg_a < w * h + val l_limit = if (hasAlpha) 5 else 7 // Use fewer luminance bits if there's alpha + val lx = Math.max(1, Math.round((l_limit * w).toFloat() / Math.max(w, h).toFloat())) + val ly = Math.max(1, Math.round((l_limit * h).toFloat() / Math.max(w, h).toFloat())) + val l = FloatArray(w * h) // luminance + val p = FloatArray(w * h) // yellow - blue + val q = FloatArray(w * h) // red - green + val a = FloatArray(w * h) // alpha + + // Convert the image from RGBA to LPQA (composite atop the average color) + i = 0 + while (i < w * h) { + val alpha = (Color.alpha(pixels[i]) and 255) / 255.0f + val r = avg_r * (1.0f - alpha) + alpha / 255.0f * Color.red(pixels[i]) + val g = avg_g * (1.0f - alpha) + alpha / 255.0f * Color.green(pixels[i]) + val b = avg_b * (1.0f - alpha) + alpha / 255.0f * Color.blue(pixels[i]) + l[i] = (r + g + b) / 3.0f + p[i] = (r + g) / 2.0f - b + q[i] = r - g + a[i] = alpha + i++ + } + + // Encode using the DCT into DC (constant) and normalized AC (varying) terms + val l_channel = Channel(Math.max(3, lx), Math.max(3, ly)).encode(w, h, l) + val p_channel = Channel(3, 3).encode(w, h, p) + val q_channel = Channel(3, 3).encode(w, h, q) + val a_channel = if (hasAlpha) Channel(5, 5).encode(w, h, a) else null + + // Write the constants + val isLandscape = w > h + val header24 = ( + Math.round(63.0f * l_channel.dc) + or (Math.round(31.5f + 31.5f * p_channel.dc) shl 6) + or (Math.round(31.5f + 31.5f * q_channel.dc) shl 12) + or (Math.round(31.0f * l_channel.scale) shl 18) + or if (hasAlpha) 1 shl 23 else 0 + ) + val header16 = ( + (if (isLandscape) ly else lx) + or (Math.round(63.0f * p_channel.scale) shl 3) + or (Math.round(63.0f * q_channel.scale) shl 9) + or if (isLandscape) 1 shl 15 else 0 + ) + val ac_start = if (hasAlpha) 6 else 5 + val ac_count = ( + l_channel.ac.size + p_channel.ac.size + q_channel.ac.size + + if (hasAlpha) a_channel!!.ac.size else 0 + ) + val hash = ByteArray(ac_start + (ac_count + 1) / 2) + hash[0] = header24.toByte() + hash[1] = (header24 shr 8).toByte() + hash[2] = (header24 shr 16).toByte() + hash[3] = header16.toByte() + hash[4] = (header16 shr 8).toByte() + if (hasAlpha) { + hash[5] = ( + Math.round(15.0f * a_channel!!.dc) + or (Math.round(15.0f * a_channel.scale) shl 4) + ).toByte() + } + + // Write the varying factors + var ac_index = 0 + ac_index = l_channel.writeTo(hash, ac_start, ac_index) + ac_index = p_channel.writeTo(hash, ac_start, ac_index) + ac_index = q_channel.writeTo(hash, ac_start, ac_index) + if (hasAlpha) a_channel!!.writeTo(hash, ac_start, ac_index) + + return hash + } + + private fun resizeKeepingAspectRatio(bitmap: Bitmap, maxSize: Int): Bitmap { + val width = bitmap.width + val height = bitmap.height + val ratio = width.toFloat() / height.toFloat() + + val newWidth: Int + val newHeight: Int + + if (ratio > 1) { + newWidth = maxSize + newHeight = (maxSize / ratio).toInt() + } else { + newHeight = maxSize + newWidth = (maxSize * ratio).toInt() + } + + return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true) + } + + private class Channel(var nx: Int, var ny: Int) { + var dc = 0f + var ac: FloatArray + var scale = 0f + + init { + var n = 0 + for (cy in 0 until ny) { + var cx = if (cy > 0) 0 else 1 + while (cx * ny < nx * (ny - cy)) { + n++ + cx++ + } + } + ac = FloatArray(n) + } + + fun encode(w: Int, h: Int, channel: FloatArray): Channel { + var n = 0 + val fx = FloatArray(w) + for (cy in 0 until ny) { + var cx = 0 + while (cx * ny < nx * (ny - cy)) { + var f = 0f + for (x in 0 until w) fx[x] = Math.cos(Math.PI / w * cx * (x + 0.5f)).toFloat() + for (y in 0 until h) { + val fy = Math.cos(Math.PI / h * cy * (y + 0.5f)).toFloat() + for (x in 0 until w) f += channel[x + y * w] * fx[x] * fy + } + f /= (w * h).toFloat() + if (cx > 0 || cy > 0) { + ac[n++] = f + scale = Math.max(scale, Math.abs(f)) + } else { + dc = f + } + cx++ + } + } + if (scale > 0) for (i in ac.indices) ac[i] = 0.5f + 0.5f / scale * ac[i] + return this + } + + fun writeTo(hash: ByteArray, start: Int, index: Int): Int { + var currentIndex = index + for (v in ac) { + hash[start + (currentIndex shr 1)] = (hash[start + (currentIndex shr 1)].toInt() or (Math.round(15.0f * v) shl (currentIndex and 1 shl 2))).toByte() + currentIndex++ + } + return currentIndex + } + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashFetcher.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashFetcher.kt new file mode 100644 index 00000000..48567170 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashFetcher.kt @@ -0,0 +1,31 @@ +package expo.modules.image.thumbhash + +import android.graphics.Bitmap +import android.util.Base64 +import com.bumptech.glide.Priority +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.data.DataFetcher +import expo.modules.kotlin.exception.CodedException + +class ThumbhashDecodingFailure(thumbhash: String?, cause: Exception?) : CodedException( + message = "Cannot decode provided thumbhash '$thumbhash' $cause" +) + +class ThumbhashFetcher( + private val thumbhash: String? +) : DataFetcher { + override fun cleanup() = Unit + override fun cancel() = Unit + override fun getDataClass(): Class = Bitmap::class.java + override fun getDataSource(): DataSource = DataSource.LOCAL + + override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + try { + val decodedThumbhash = Base64.decode(thumbhash, Base64.DEFAULT) + val bitmap = ThumbhashDecoder.thumbHashToBitmap(decodedThumbhash) + callback.onDataReady(bitmap) + } catch (e: Exception) { + callback.onLoadFailed(ThumbhashDecodingFailure(thumbhash, e)) + } + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModel.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModel.kt new file mode 100644 index 00000000..012c21ce --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModel.kt @@ -0,0 +1,7 @@ +package expo.modules.image.thumbhash + +import android.net.Uri + +data class ThumbhashModel( + val uri: Uri +) diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoader.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoader.kt new file mode 100644 index 00000000..c727bce5 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoader.kt @@ -0,0 +1,24 @@ +package expo.modules.image.thumbhash + +import android.graphics.Bitmap + +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.signature.ObjectKey + +class ThumbhashModelLoader : ModelLoader { + + override fun handles(model: ThumbhashModel): Boolean = true + + override fun buildLoadData(model: ThumbhashModel, width: Int, height: Int, options: Options): ModelLoader.LoadData { + // The URI looks like this: thumbhash:/3OcRJYB4d3h\iIeHeEh3eIhw+j2w + // ThumbHash may include slashes which could break the structure of the URL, so we replace them + // with backslashes on the JS side and revert them back to slashes here, before generating the image. + val thumbhash = model.uri.pathSegments.joinToString(separator = "/").replace("\\", "/") + + return ModelLoader.LoadData( + ObjectKey(model), + ThumbhashFetcher(thumbhash) + ) + } +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoaderFactory.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoaderFactory.kt new file mode 100644 index 00000000..b492dd07 --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModelLoaderFactory.kt @@ -0,0 +1,13 @@ +package expo.modules.image.thumbhash + +import android.graphics.Bitmap +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.load.model.ModelLoaderFactory +import com.bumptech.glide.load.model.MultiModelLoaderFactory + +class ThumbhashModelLoaderFactory : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader = + ThumbhashModelLoader() + + override fun teardown() = Unit +} diff --git a/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModule.kt b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModule.kt new file mode 100644 index 00000000..e22ac36f --- /dev/null +++ b/node_modules/expo-image/android/src/main/java/expo/modules/image/thumbhash/ThumbhashModule.kt @@ -0,0 +1,16 @@ +package expo.modules.image.thumbhash + +import android.content.Context +import android.graphics.Bitmap +import com.bumptech.glide.Glide +import com.bumptech.glide.Registry +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.module.LibraryGlideModule + +@GlideModule +class ThumbhashModule : LibraryGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + super.registerComponents(context, glide, registry) + registry.prepend(ThumbhashModel::class.java, Bitmap::class.java, ThumbhashModelLoaderFactory()) + } +} diff --git a/node_modules/expo-image/expo-module.config.json b/node_modules/expo-image/expo-module.config.json new file mode 100644 index 00000000..51198363 --- /dev/null +++ b/node_modules/expo-image/expo-module.config.json @@ -0,0 +1,15 @@ +{ + "platforms": ["apple", "android"], + "apple": { + "modules": ["ImageModule"] + }, + "android": { + "modules": ["expo.modules.image.ExpoImageModule"], + "publication": { + "groupId": "BareExpo", + "artifactId": "expo.modules.image", + "version": "3.0.11", + "repository": "local-maven-repo" + } + } +} diff --git a/node_modules/expo-image/ios/AnimatedImage.swift b/node_modules/expo-image/ios/AnimatedImage.swift new file mode 100644 index 00000000..34f8fafb --- /dev/null +++ b/node_modules/expo-image/ios/AnimatedImage.swift @@ -0,0 +1,37 @@ +// Copyright 2024-present 650 Industries. All rights reserved. + +import SDWebImage + +/** + Custom `SDAnimatedImage` that fixes issues with `images` and `duration` not being available. + */ +final class AnimatedImage: SDAnimatedImage { + var frames: [SDImageFrame]? + + // MARK: - UIImage + + override var images: [UIImage]? { + preloadAllFrames() + return frames?.map({ $0.image }) + } + + override var duration: TimeInterval { + preloadAllFrames() + return frames?.reduce(0, { $0 + $1.duration }) ?? 0.0 + } + + // MARK: - SDAnimatedImage + + override func preloadAllFrames() { + if frames != nil { + return + } + frames = [UInt](0.. CGImage? { + guard blurhash.count >= 6 else { + return nil + } + + let sizeFlag = decode83(String(blurhash[0])) + let numY = (sizeFlag / 9) + 1 + let numX = (sizeFlag % 9) + 1 + + let quantisedMaximumValue = decode83(String(blurhash[1])) + let maximumValue = Float(quantisedMaximumValue + 1) / 166 + + guard blurhash.count == 4 + 2 * numX * numY else { + return nil + } + + let colors: [(Float, Float, Float)] = (0 ..< numX * numY).map { i in + if i == 0 { + let value = decode83(String(blurhash[2 ..< 6])) + return decodeDC(value) + } else { + let value = decode83(String(blurhash[4 + i * 2 ..< 4 + i * 2 + 2])) + return decodeAC(value, maximumValue: maximumValue * punch) + } + } + + let width = Int(size.width) + let height = Int(size.height) + let bytesPerRow = width * 3 + + guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else { + return nil + } + + CFDataSetLength(data, bytesPerRow * height) + + guard let pixels = CFDataGetMutableBytePtr(data) else { + return nil + } + + for y in 0 ..< height { + for x in 0 ..< width { + var r: Float = 0 + var g: Float = 0 + var b: Float = 0 + + for j in 0 ..< numY { + for i in 0 ..< numX { + let basis = cos(Float.pi * Float(x) * Float(i) / Float(width)) * cos(Float.pi * Float(y) * Float(j) / Float(height)) + let color = colors[i + j * numX] + r += color.0 * basis + g += color.1 * basis + b += color.2 * basis + } + } + + let intR = UInt8(linearTosRGB(r)) + let intG = UInt8(linearTosRGB(g)) + let intB = UInt8(linearTosRGB(b)) + + pixels[3 * x + 0 + y * bytesPerRow] = intR + pixels[3 * x + 1 + y * bytesPerRow] = intG + pixels[3 * x + 2 + y * bytesPerRow] = intB + } + } + + let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue) + + guard let provider = CGDataProvider(data: data), let cgImage = CGImage( + width: width, + height: height, + bitsPerComponent: 8, + bitsPerPixel: 24, + bytesPerRow: bytesPerRow, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: bitmapInfo, + provider: provider, + decode: nil, + shouldInterpolate: true, + intent: .defaultIntent + ) else { + return nil + } + + return cgImage +} + +internal func blurhash(fromImage image: UIImage, numberOfComponents components: (Int, Int)) -> String? { + let size = image.size + let scale = image.scale + + let pixelWidth = Int(round(size.width * scale)) + let pixelHeight = Int(round(size.height * scale)) + + let context = CGContext( + data: nil, + width: pixelWidth, + height: pixelHeight, + bitsPerComponent: 8, + bytesPerRow: pixelWidth * 4, + space: CGColorSpace(name: CGColorSpace.sRGB)!, + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue + )! + context.scaleBy(x: scale, y: -scale) + context.translateBy(x: 0, y: -size.height) + + UIGraphicsPushContext(context) + image.draw(at: .zero) + UIGraphicsPopContext() + + guard let cgImage = context.makeImage(), + let dataProvider = cgImage.dataProvider, + let data = dataProvider.data, + let pixels = CFDataGetBytePtr(data) else { + assertionFailure("Unexpected error!") + return nil + } + + let width = cgImage.width + let height = cgImage.height + let bytesPerRow = cgImage.bytesPerRow + + var factors: [(Float, Float, Float)] = [] + for y in 0 ..< components.1 { + for x in 0 ..< components.0 { + let normalisation: Float = (x == 0 && y == 0) ? 1 : 2 + let factor = multiplyBasisFunction( + pixels: pixels, + width: width, + height: height, + bytesPerRow: bytesPerRow, + bytesPerPixel: cgImage.bitsPerPixel / 8, + pixelOffset: 0 + ) { + normalisation * cos(Float.pi * Float(x) * $0 / Float(width)) as Float * cos(Float.pi * Float(y) * $1 / Float(height)) as Float + } + factors.append(factor) + } + } + + let dc = factors.first! + let ac = factors.dropFirst() + + var hash = "" + + let sizeFlag = (components.0 - 1) + (components.1 - 1) * 9 + hash += encode83(sizeFlag, length: 1) + + let maximumValue: Float + if !ac.isEmpty { + let actualMaximumValue = ac.map({ max(abs($0.0), abs($0.1), abs($0.2)) }).max()! + let quantisedMaximumValue = Int(max(0, min(82, floor(actualMaximumValue * 166 - 0.5)))) + maximumValue = Float(quantisedMaximumValue + 1) / 166 + hash += encode83(quantisedMaximumValue, length: 1) + } else { + maximumValue = 1 + hash += encode83(0, length: 1) + } + + hash += encode83(encodeDC(dc), length: 4) + + for factor in ac { + hash += encode83(encodeAC(factor, maximumValue: maximumValue), length: 2) + } + + return hash +} + +internal func isBlurhash(_ str: String) -> Bool { + return str.allSatisfy { char in decodeCharacters[char] != nil } +} + +// MARK: - Encode + +// swiftlint:disable:next function_parameter_count +private func multiplyBasisFunction( + pixels: UnsafePointer, + width: Int, + height: Int, + bytesPerRow: Int, + bytesPerPixel: Int, + pixelOffset: Int, + basisFunction: (Float, Float) -> Float +) -> (Float, Float, Float) { + var r: Float = 0 + var g: Float = 0 + var b: Float = 0 + + let buffer = UnsafeBufferPointer(start: pixels, count: height * bytesPerRow) + + for x in 0 ..< width { + for y in 0 ..< height { + let basis = basisFunction(Float(x), Float(y)) + r += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 0 + y * bytesPerRow]) + g += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 1 + y * bytesPerRow]) + b += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 2 + y * bytesPerRow]) + } + } + + let scale = 1 / Float(width * height) + + return (r * scale, g * scale, b * scale) +} + +private let encodeCharacters: [Character] = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~") + +private func encodeDC(_ value: (Float, Float, Float)) -> Int { + let roundedR = linearTosRGB(value.0) + let roundedG = linearTosRGB(value.1) + let roundedB = linearTosRGB(value.2) + return (roundedR << 16) + (roundedG << 8) + roundedB +} + +private func encodeAC(_ value: (Float, Float, Float), maximumValue: Float) -> Int { + let quantR = Int(max(0, min(18, floor(signPow(value.0 / maximumValue, 0.5) * 9 + 9.5)))) + let quantG = Int(max(0, min(18, floor(signPow(value.1 / maximumValue, 0.5) * 9 + 9.5)))) + let quantB = Int(max(0, min(18, floor(signPow(value.2 / maximumValue, 0.5) * 9 + 9.5)))) + + return quantR * 19 * 19 + quantG * 19 + quantB +} + +private func encode83(_ value: Int, length: Int) -> String { + var result = "" + for i in 1 ... length { + let digit = (value / pow(83, length - i)) % 83 + result += String(encodeCharacters[Int(digit)]) + } + return result +} + +// MARK: - Decode + +private func decodeDC(_ value: Int) -> (Float, Float, Float) { + let intR = value >> 16 + let intG = (value >> 8) & 255 + let intB = value & 255 + return (sRGBToLinear(intR), sRGBToLinear(intG), sRGBToLinear(intB)) +} + +private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float) { + let quantR = value / (19 * 19) + let quantG = (value / 19) % 19 + let quantB = value % 19 + + let rgb = ( + signPow((Float(quantR) - 9) / 9, 2) * maximumValue, + signPow((Float(quantG) - 9) / 9, 2) * maximumValue, + signPow((Float(quantB) - 9) / 9, 2) * maximumValue + ) + + return rgb +} + +private let decodeCharacters: [Character: Int] = { + var dict: [Character: Int] = [:] + for (index, character) in encodeCharacters.enumerated() { + dict[character] = index + } + return dict +}() + +private func decode83(_ str: String) -> Int { + var value: Int = 0 + for character in str { + if let digit = decodeCharacters[character] { + value = value * 83 + digit + } + } + return value +} + +// MARK: - Helpers + +private func signPow(_ value: Float, _ exp: Float) -> Float { + return copysign(pow(abs(value), exp), value) +} + +private func linearTosRGB(_ value: Float) -> Int { + let v = max(0, min(1, value)) + if v <= 0.0031308 { + return Int(v * 12.92 * 255 + 0.5) + } else { + return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) + } +} + +private func sRGBToLinear(_ value: Type) -> Float { + let v = Float(Int64(value)) / 255 + if v <= 0.04045 { + return v / 12.92 + } else { + return pow((v + 0.055) / 1.055, 2.4) + } +} + +private func pow(_ base: Int, _ exponent: Int) -> Int { + return (0 ..< exponent).reduce(1) { value, _ in value * base } +} + +private extension String { + subscript (offset: Int) -> Character { + return self[index(startIndex, offsetBy: offset)] + } + + subscript (bounds: CountableClosedRange) -> Substring { + let start = index(startIndex, offsetBy: bounds.lowerBound) + let end = index(startIndex, offsetBy: bounds.upperBound) + return self[start...end] + } + + subscript (bounds: CountableRange) -> Substring { + let start = index(startIndex, offsetBy: bounds.lowerBound) + let end = index(startIndex, offsetBy: bounds.upperBound) + return self[start.. Bool { + return url?.scheme == "blurhash" + } + + func requestImage( + with url: URL?, + options: SDWebImageOptions = [], + context: [SDWebImageContextOption: Any]?, + progress progressBlock: SDImageLoaderProgressBlock?, + completed completedBlock: SDImageLoaderCompletedBlock? = nil + ) -> SDWebImageOperation? { + guard let url = url else { + let error = makeNSError(description: "URL provided to BlurhashLoader is missing") + completedBlock?(nil, nil, error, false) + return nil + } + guard let source = context?[ImageView.contextSourceKey] as? ImageSource else { + let error = makeNSError(description: "Image source was not provided to the context") + completedBlock?(nil, nil, error, false) + return nil + } + + // The URI looks like this: blurhash:/WgF}G?az0fs.x[jat7xFRjNHt6s.4;oe-:RkVtkCi^Nbo|xZRjWB + // Which means that the `pathComponents[0]` is `/` and we need to skip it to get the hash. + let blurhash = url.pathComponents[1] + let size = CGSize(width: source.width, height: source.height) + + DispatchQueue.global(qos: .userInitiated).async { + if let image = image(fromBlurhash: blurhash, size: size) { + DispatchQueue.main.async { + completedBlock?(UIImage(cgImage: image), nil, nil, true) + } + } else { + let error = makeNSError(description: "Unable to generate an image from the given blurhash") + completedBlock?(nil, nil, error, false) + } + } + return nil + } + + func shouldBlockFailedURL(with url: URL, error: Error) -> Bool { + // If the algorithm failed to generate an image from the url, + // it's not possible that next time it will work :) + return true + } +} diff --git a/node_modules/expo-image/ios/ContentFit.swift b/node_modules/expo-image/ios/ContentFit.swift new file mode 100644 index 00000000..4f2b4a92 --- /dev/null +++ b/node_modules/expo-image/ios/ContentFit.swift @@ -0,0 +1,55 @@ +import ExpoModulesCore + +/** + Describes how the image should be resized to fit its container. + - Note: It mirrors the CSS [`object-fit`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) property. + */ +enum ContentFit: String, Enumerable { + /** + The image is scaled to maintain its aspect ratio while fitting within the container's box. + The entire image is made to fill the box, while preserving its aspect ratio, + so the image will be "letterboxed" if its aspect ratio does not match the aspect ratio of the box. + */ + case contain + + /** + The image is sized to maintain its aspect ratio while filling the element's entire content box. + If the image's aspect ratio does not match the aspect ratio of its box, then the object will be clipped to fit. + */ + case cover + + /** + The image is sized to fill the element's content box. The entire object will completely fill the box. + If the image's aspect ratio does not match the aspect ratio of its box, then the image will be stretched to fit. + */ + case fill + + /** + The image is not resized and is centered by default. + When specified, the exact position can be controlled with `ContentPosition`. + */ + case none + + /** + The image is sized as if `none` or `contain` were specified, + whichever would result in a smaller concrete image size. + */ + case scaleDown = "scale-down" + + /** + `ContentFit` cases can be directly translated to the native `UIView.ContentMode` + except `scaleDown` that needs to be handled differently at the later step of rendering. + */ + func toContentMode() -> UIView.ContentMode { + switch self { + case .contain: + return .scaleAspectFit + case .cover: + return .scaleAspectFill + case .fill: + return .scaleToFill + case .none, .scaleDown: + return .center + } + } +} diff --git a/node_modules/expo-image/ios/ContentPosition.swift b/node_modules/expo-image/ios/ContentPosition.swift new file mode 100644 index 00000000..73c34ca6 --- /dev/null +++ b/node_modules/expo-image/ios/ContentPosition.swift @@ -0,0 +1,108 @@ +import ExpoModulesCore + +/** + Represents a position value that might be either `Double` or `String`. + */ +typealias ContentPositionValue = Either + +/** + Specifies the alignment of the image within the container's box. + - Note: Its intention is to behave like the CSS [`object-position`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) property. + */ +struct ContentPosition: Record { + static let center = Self() + + @Field + var top: ContentPositionValue? + + @Field + var bottom: ContentPositionValue? + + @Field + var right: ContentPositionValue? + + @Field + var left: ContentPositionValue? + + /** + Returns a horizontal content offset based on the `left` or `right` field. + */ + func offsetX(contentWidth: Double, containerWidth: Double) -> Double { + let diff = containerWidth - contentWidth + + if let leftDistance = distance(from: left) { + return -diff / 2 + leftDistance + } + if let rightDistance = distance(from: right) { + return diff / 2 - rightDistance + } + if let factor = factor(from: left) { + return -diff / 2 + diff * factor + } + if let factor = factor(from: right) { + return diff / 2 - diff * factor + } + return 0 + } + + /** + Returns a vertical content offset based on the `top` or `bottom` field. + */ + func offsetY(contentHeight: Double, containerHeight: Double) -> Double { + let diff = containerHeight - contentHeight + + if let topDistance = distance(from: top) { + return -diff / 2 + topDistance + } + if let bottomDistance = distance(from: bottom) { + return diff / 2 - bottomDistance + } + if let factor = factor(from: top) { + return -diff / 2 + diff * factor + } + if let factor = factor(from: bottom) { + return diff / 2 - diff * factor + } + return 0 + } + + /** + A `CGPoint` with horizontal and vertical content offsets. + */ + func offset(contentSize: CGSize, containerSize: CGSize) -> CGPoint { + return CGPoint( + x: offsetX(contentWidth: contentSize.width, containerWidth: containerSize.width), + y: offsetY(contentHeight: contentSize.height, containerHeight: containerSize.height) + ) + } +} + +/** + Returns a static offset from the given position value or `nil` when it cannot be cast to a `Double`. + */ +private func distance(from value: ContentPositionValue?) -> Double? { + if let value: Double = value?.get() { + return value + } + if let value: String = value?.get() { + return Double(value) + } + return nil +} + +/** + Returns a factor from the percentage value from the given position. + The value must be a string containing a number and `%` character, or equal to `"center"` which is an equivalent to `50%`. + */ +private func factor(from value: ContentPositionValue?) -> Double? { + guard let value: String = value?.get() else { + return nil + } + if value == "center" { + return 0.5 + } + guard value.contains("%"), let percentage = Double(value.replacingOccurrences(of: "%", with: "")) else { + return nil + } + return percentage / 100 +} diff --git a/node_modules/expo-image/ios/ExpoImage.podspec b/node_modules/expo-image/ios/ExpoImage.podspec new file mode 100644 index 00000000..818e0505 --- /dev/null +++ b/node_modules/expo-image/ios/ExpoImage.podspec @@ -0,0 +1,41 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json'))) + +Pod::Spec.new do |s| + s.name = 'ExpoImage' + s.version = package['version'] + s.summary = package['description'] + s.description = package['description'] + s.license = package['license'] + s.author = package['author'] + s.homepage = package['homepage'] + s.platforms = { + :ios => '15.1', + :tvos => '15.1' + } + s.swift_version = '5.9' + s.source = { git: 'https://github.com/expo/expo.git' } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + s.dependency 'SDWebImage', '~> 5.21.0' + s.dependency 'SDWebImageAVIFCoder', '~> 0.11.0' + s.dependency 'SDWebImageSVGCoder', '~> 1.7.0' + s.dependency 'SDWebImageWebPCoder', '~> 0.14.6' + s.dependency 'libavif/libdav1d' + + # Swift/Objective-C compatibility + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + } + + s.source_files = "**/*.{h,m,swift}" + s.exclude_files = 'Tests/' + + s.test_spec 'Tests' do |test_spec| + test_spec.dependency 'ExpoModulesTestCore' + + test_spec.source_files = 'Tests/**/*.{m,swift}' + end +end diff --git a/node_modules/expo-image/ios/Image.swift b/node_modules/expo-image/ios/Image.swift new file mode 100644 index 00000000..5c92efa2 --- /dev/null +++ b/node_modules/expo-image/ios/Image.swift @@ -0,0 +1,20 @@ +// Copyright 2024-present 650 Industries. All rights reserved. + +import ExpoModulesCore + +internal final class Image: SharedRef { + override var nativeRefType: String { + "image" + } + + var isAnimated: Bool { + return !(ref.images?.isEmpty ?? true) + } + + override func getAdditionalMemoryPressure() -> Int { + guard let cgImage = ref.cgImage else { + return 0 + } + return cgImage.bytesPerRow * cgImage.height + } +} diff --git a/node_modules/expo-image/ios/ImageCachePolicy.swift b/node_modules/expo-image/ios/ImageCachePolicy.swift new file mode 100644 index 00000000..dbf608b0 --- /dev/null +++ b/node_modules/expo-image/ios/ImageCachePolicy.swift @@ -0,0 +1,24 @@ +// Copyright 2022-present 650 Industries. All rights reserved. + +import SDWebImage +import ExpoModulesCore + +enum ImageCachePolicy: String, Enumerable { + case none = "none" + case disk = "disk" + case memory = "memory" + case memoryAndDisk = "memory-disk" + + func toSdCacheType() -> SDImageCacheType { + switch self { + case .none: + return .none + case .disk: + return .disk + case .memory: + return .memory + case .memoryAndDisk: + return .all + } + } +} diff --git a/node_modules/expo-image/ios/ImageCacheType.swift b/node_modules/expo-image/ios/ImageCacheType.swift new file mode 100644 index 00000000..cd7f51bb --- /dev/null +++ b/node_modules/expo-image/ios/ImageCacheType.swift @@ -0,0 +1,24 @@ +// Copyright 2022-present 650 Industries. All rights reserved. + +import SDWebImage +import ExpoModulesCore + +enum ImageCacheType: String, Enumerable { + case none + case disk + case memory + + static func fromSdCacheType(_ sdImageCacheType: SDImageCacheType) -> ImageCacheType { + switch sdImageCacheType { + case .none: + return .none + case .disk, .all: + return .disk + case .memory: + return .memory + @unknown default: + log.error("Unhandled `SDImageCacheType` value: \(sdImageCacheType), returning `none` as fallback. Add the missing case as soon as possible.") + return .none + } + } +} diff --git a/node_modules/expo-image/ios/ImageLoadOptions.swift b/node_modules/expo-image/ios/ImageLoadOptions.swift new file mode 100644 index 00000000..0dbf4681 --- /dev/null +++ b/node_modules/expo-image/ios/ImageLoadOptions.swift @@ -0,0 +1,12 @@ +// Copyright 2024-present 650 Industries. All rights reserved. + +import ExpoModulesCore + +internal struct ImageLoadOptions: Record { + @Field var maxWidth: Int = .max + @Field var maxHeight: Int = .max + + func getMaxSize() -> CGSize { + return CGSize(width: maxWidth, height: maxHeight) + } +} diff --git a/node_modules/expo-image/ios/ImageLoadTask.swift b/node_modules/expo-image/ios/ImageLoadTask.swift new file mode 100644 index 00000000..7a46afbe --- /dev/null +++ b/node_modules/expo-image/ios/ImageLoadTask.swift @@ -0,0 +1,27 @@ +// Copyright 2024-present 650 Industries. All rights reserved. + +import ExpoModulesCore + +internal final class ImageLoadTask: SharedObject { + private let source: ImageSource + private let maxSize: CGSize? + private var task: Task? + + init(_ source: ImageSource, maxSize: CGSize? = nil) { + self.source = source + self.maxSize = maxSize + super.init() + } + + func load() async throws -> UIImage { + let task = self.task ?? Task { + return try await ImageLoader.shared.load(source, maxSize: maxSize) + } + self.task = task + return try await task.value + } + + func abort() { + task?.cancel() + } +} diff --git a/node_modules/expo-image/ios/ImageLoader.swift b/node_modules/expo-image/ios/ImageLoader.swift new file mode 100644 index 00000000..a0dacf9f --- /dev/null +++ b/node_modules/expo-image/ios/ImageLoader.swift @@ -0,0 +1,39 @@ +// Copyright 2024-present 650 Industries. All rights reserved. + +import SDWebImage +import ExpoModulesCore + +internal final class ImageLoader { + static let shared = ImageLoader() + + lazy var imageManager = SDWebImageManager( + cache: SDImageCache.shared, + loader: SDImageLoadersManager.shared + ) + + func load(_ source: ImageSource, maxSize: CGSize? = nil) async throws -> UIImage { + // This loader uses only the disk cache. We may want to give more control on this, but the memory cache + // doesn't make much sense for shared refs as they're kept in memory as long as their JS objects. + var context = createSDWebImageContext(forSource: source, cachePolicy: .disk) + + if let maxSize { + context[.imageThumbnailPixelSize] = maxSize + } + + return try await withCheckedThrowingContinuation { continuation in + imageManager.loadImage(with: source.uri, context: context, progress: nil) { image, _, error, _, _, _ in + if let image { + continuation.resume(returning: image) + } else { + continuation.resume(throwing: ImageLoadingFailed().causedBy(error)) + } + } + } + } +} + +internal final class ImageLoadingFailed: Exception { + override var reason: String { + "Failed to load an image" + } +} diff --git a/node_modules/expo-image/ios/ImageModule.swift b/node_modules/expo-image/ios/ImageModule.swift new file mode 100644 index 00000000..b44f4316 --- /dev/null +++ b/node_modules/expo-image/ios/ImageModule.swift @@ -0,0 +1,264 @@ +// Copyright 2022-present 650 Industries. All rights reserved. + +import ExpoModulesCore +import SDWebImage +import SDWebImageAVIFCoder +import SDWebImageSVGCoder + +public final class ImageModule: Module { + lazy var prefetcher = SDWebImagePrefetcher.shared + + public func definition() -> ModuleDefinition { + Name("ExpoImage") + + OnCreate { + ImageModule.registerCoders() + ImageModule.registerLoaders() + } + + View(ImageView.self) { + Events( + "onLoadStart", + "onProgress", + "onError", + "onLoad", + "onDisplay" + ) + + Prop("source") { (view: ImageView, sources: Either<[ImageSource], SharedRef>?) in + if let imageRef: SharedRef = sources?.get() { + // Unset an array of traditional sources and just render the image ref right away. + view.sources = nil + view.renderSourceImage(imageRef.ref) + } else { + // Update an array of sources. Image will start loading once the all props are updated. + view.sources = sources?.get() + view.sourceImage = nil + } + } + + Prop("placeholder") { (view, placeholders: [ImageSource]?) in + view.placeholderSources = placeholders ?? [] + } + + Prop("contentFit") { (view, contentFit: ContentFit?) in + view.contentFit = contentFit ?? .cover + } + + Prop("placeholderContentFit") { (view, placeholderContentFit: ContentFit?) in + view.placeholderContentFit = placeholderContentFit ?? .scaleDown + } + + Prop("contentPosition") { (view, contentPosition: ContentPosition?) in + view.contentPosition = contentPosition ?? .center + } + + Prop("transition") { (view, transition: ImageTransition?) in + view.transition = transition + } + + Prop("blurRadius") { (view, blurRadius: Double?) in + let radius = blurRadius ?? .zero + // the implementation uses Apple's CIGaussianBlur internally + // we divide the radius to achieve more consistent cross-platform appearance + // the value was found experimentally + view.blurRadius = radius / 2.0 + } + + Prop("tintColor") { (view, tintColor: UIColor?) in + view.imageTintColor = tintColor + } + + Prop("priority") { (view, priority: ImagePriority?) in + view.loadingOptions.remove([.lowPriority, .highPriority]) + + if let priority = priority?.toSDWebImageOptions() { + view.loadingOptions.insert(priority) + } + } + + Prop("cachePolicy") { (view, cachePolicy: ImageCachePolicy?) in + view.cachePolicy = cachePolicy ?? .disk + } + + Prop("enableLiveTextInteraction") { (view, enableLiveTextInteraction: Bool?) in + #if !os(tvOS) + view.enableLiveTextInteraction = enableLiveTextInteraction ?? false + #endif + } + + Prop("accessible") { (view, accessible: Bool?) in + view.sdImageView.isAccessibilityElement = accessible ?? false + } + + Prop("accessibilityLabel") { (view, label: String?) in + view.sdImageView.accessibilityLabel = label + } + + Prop("recyclingKey") { (view, key: String?) in + view.recyclingKey = key + } + + Prop("allowDownscaling") { (view, allowDownscaling: Bool?) in + view.allowDownscaling = allowDownscaling ?? true + } + + Prop("autoplay") { (view, autoplay: Bool?) in + view.autoplay = autoplay ?? true + } + + Prop("useAppleWebpCodec", true) { (view, useAppleWebpCodec: Bool) in + view.useAppleWebpCodec = useAppleWebpCodec + } + + Prop("enforceEarlyResizing", false) { (view, enforceEarlyResizing: Bool) in + view.enforceEarlyResizing = enforceEarlyResizing + } + + AsyncFunction("startAnimating") { (view: ImageView) in + view.sdImageView.startAnimating() + } + + AsyncFunction("stopAnimating") { (view: ImageView) in + view.sdImageView.stopAnimating() + } + + AsyncFunction("lockResourceAsync") { (view: ImageView) in + view.lockResource = true + } + + AsyncFunction("unlockResourceAsync") { (view: ImageView) in + view.lockResource = false + } + + AsyncFunction("reloadAsync") { (view: ImageView) in + view.reload(force: true) + } + + OnViewDidUpdateProps { view in + view.reload() + } + } + + AsyncFunction("prefetch") { (urls: [URL], cachePolicy: ImageCachePolicy, headersMap: [String: String]?, promise: Promise) in + var context = SDWebImageContext() + let sdCacheType = cachePolicy.toSdCacheType().rawValue + context[.queryCacheType] = SDImageCacheType.none.rawValue + context[.storeCacheType] = SDImageCacheType.none.rawValue + context[.originalQueryCacheType] = sdCacheType + context[.originalStoreCacheType] = sdCacheType + + var imagesLoaded = 0 + var failed = false + + if headersMap != nil { + context[.downloadRequestModifier] = SDWebImageDownloaderRequestModifier(headers: headersMap) + } + + urls.forEach { url in + SDWebImagePrefetcher.shared.prefetchURLs([url], context: context, progress: nil, completed: { _, skipped in + if skipped > 0 && !failed { + failed = true + promise.resolve(false) + } else { + imagesLoaded = imagesLoaded + 1 + if imagesLoaded == urls.count { + promise.resolve(true) + } + } + }) + } + } + + AsyncFunction("generateBlurhashAsync") { (source: Either, numberOfComponents: CGSize, promise: Promise) in + let parsedNumberOfComponents = (width: Int(numberOfComponents.width), height: Int(numberOfComponents.height)) + generatePlaceholder(source: source) { (image: UIImage) in + if let blurhashString = blurhash(fromImage: image, numberOfComponents: parsedNumberOfComponents) { + promise.resolve(blurhashString) + } else { + promise.reject(BlurhashGenerationException()) + } + } + } + + AsyncFunction("generateThumbhashAsync") { (source: Either, promise: Promise) in + generatePlaceholder(source: source) { (image: UIImage) in + let blurhashString = thumbHash(fromImage: image) + promise.resolve(blurhashString.base64EncodedString()) + } + } + + AsyncFunction("clearMemoryCache") { () -> Bool in + SDImageCache.shared.clearMemory() + return true + } + + AsyncFunction("clearDiskCache") { (promise: Promise) in + SDImageCache.shared.clearDisk { + promise.resolve(true) + } + } + + AsyncFunction("getCachePathAsync") { (cacheKey: String, promise: Promise) in + /* + We need to check if the image exists in the cache first since `cachePath` will + return a path regardless of whether or not the image exists. + */ + SDImageCache.shared.diskImageExists(withKey: cacheKey) { exists in + if exists { + let cachePath = SDImageCache.shared.cachePath(forKey: cacheKey) + + promise.resolve(cachePath) + } else { + promise.resolve(nil) + } + } + } + + AsyncFunction("loadAsync") { (source: ImageSource, options: ImageLoadOptions?) -> Image? in + let image = try await ImageLoadTask(source, maxSize: options?.getMaxSize()).load() + return Image(image) + } + + Class(Image.self) { + Property("width", \.ref.size.width) + Property("height", \.ref.size.height) + Property("scale", \.ref.scale) + Property("isAnimated", \.isAnimated) + Property("mediaType") { image in + return imageFormatToMediaType(image.ref.sd_imageFormat) + } + } + } + + func generatePlaceholder( + source: Either, + generator: @escaping (UIImage) -> Void + ) { + if let image: Image = source.get() { + generator(image.ref) + } else if let url: URL = source.get() { + let downloader = SDWebImageDownloader() + downloader.downloadImage(with: url, progress: nil, completed: { image, _, _, _ in + DispatchQueue.global().async { + if let downloadedImage = image { + generator(downloadedImage) + } + } + }) + } + } + + static func registerCoders() { + SDImageCodersManager.shared.addCoder(WebPCoder.shared) + SDImageCodersManager.shared.addCoder(SDImageAVIFCoder.shared) + SDImageCodersManager.shared.addCoder(SDImageSVGCoder.shared) + SDImageCodersManager.shared.addCoder(SDImageHEICCoder.shared) + } + + static func registerLoaders() { + SDImageLoadersManager.shared.addLoader(BlurhashLoader()) + SDImageLoadersManager.shared.addLoader(ThumbhashLoader()) + SDImageLoadersManager.shared.addLoader(PhotoLibraryAssetLoader()) + } +} diff --git a/node_modules/expo-image/ios/ImagePriority.swift b/node_modules/expo-image/ios/ImagePriority.swift new file mode 100644 index 00000000..5c74f58e --- /dev/null +++ b/node_modules/expo-image/ios/ImagePriority.swift @@ -0,0 +1,22 @@ +import ExpoModulesCore +import SDWebImage + +enum ImagePriority: String, Enumerable { + case low + case normal + case high + + /** + Maps the priority to `SDWebImageOptions` which is a bitmask thus has only low and high priority options. + */ + func toSDWebImageOptions() -> SDWebImageOptions? { + switch self { + case .low: + return .lowPriority + case .high: + return .highPriority + default: + return nil + } + } +} diff --git a/node_modules/expo-image/ios/ImageSource.swift b/node_modules/expo-image/ios/ImageSource.swift new file mode 100644 index 00000000..e6228c27 --- /dev/null +++ b/node_modules/expo-image/ios/ImageSource.swift @@ -0,0 +1,43 @@ +// Copyright 2022-present 650 Industries. All rights reserved. + +import ExpoModulesCore + +struct ImageSource: Record { + @Field + var width: Double = 0.0 + + @Field + var height: Double = 0.0 + + @Field + var uri: URL? = nil + + @Field + var scale: Double = 1.0 + + @Field + var headers: [String: String]? + + @Field + var cacheKey: String? + + var pixelCount: Double { + return width * height * scale * scale + } + + var isBlurhash: Bool { + return uri?.scheme == "blurhash" + } + + var isThumbhash: Bool { + return uri?.scheme == "thumbhash" + } + + var isPhotoLibraryAsset: Bool { + return isPhotoLibraryAssetUrl(uri) + } + + var cacheOriginalImage: Bool { + return !isPhotoLibraryAsset + } +} diff --git a/node_modules/expo-image/ios/ImageTransition.swift b/node_modules/expo-image/ios/ImageTransition.swift new file mode 100644 index 00000000..41c6745e --- /dev/null +++ b/node_modules/expo-image/ios/ImageTransition.swift @@ -0,0 +1,67 @@ +// Copyright 2022-present 650 Industries. All rights reserved. + +import ExpoModulesCore + +enum ImageTransitionTiming: String, Enumerable { + case easeInOut = "ease-in-out" + case easeIn = "ease-in" + case easeOut = "ease-out" + case linear = "linear" + + func toAnimationOption() -> UIView.AnimationOptions { + switch self { + case .easeInOut: + return .curveEaseInOut + case .easeIn: + return .curveEaseIn + case .easeOut: + return .curveEaseOut + case .linear: + return .curveLinear + } + } +} + +enum ImageTransitionEffect: String, Enumerable { + case crossDissolve = "cross-dissolve" + case flipFromTop = "flip-from-top" + case flipFromRight = "flip-from-right" + case flipFromBottom = "flip-from-bottom" + case flipFromLeft = "flip-from-left" + case curlUp = "curl-up" + case curlDown = "curl-down" + + func toAnimationOption() -> UIView.AnimationOptions { + switch self { + case .crossDissolve: + return .transitionCrossDissolve + case .flipFromLeft: + return .transitionFlipFromLeft + case .flipFromRight: + return .transitionFlipFromRight + case .flipFromTop: + return .transitionFlipFromTop + case .flipFromBottom: + return .transitionFlipFromBottom + case .curlUp: + return .transitionCurlUp + case .curlDown: + return .transitionCurlDown + } + } +} + +struct ImageTransition: Record { + @Field + var duration: Double = 100 + + @Field + var timing: ImageTransitionTiming = .easeInOut + + @Field + var effect: ImageTransitionEffect = .crossDissolve + + func toAnimationOptions() -> UIView.AnimationOptions { + return [timing.toAnimationOption(), effect.toAnimationOption()] + } +} diff --git a/node_modules/expo-image/ios/ImageUtils.swift b/node_modules/expo-image/ios/ImageUtils.swift new file mode 100644 index 00000000..be40a77e --- /dev/null +++ b/node_modules/expo-image/ios/ImageUtils.swift @@ -0,0 +1,265 @@ +// Copyright 2022-present 650 Industries. All rights reserved. + +import SDWebImage +import ExpoModulesCore + +/** + An exception to throw when it its not possible to generate a blurhash for a given URL. + */ +public final class BlurhashGenerationException: Exception { + override public var reason: String { + "Unable to generate blurhash, make sure the image exists at the given URL" + } +} + +func cacheTypeToString(_ cacheType: SDImageCacheType) -> String { + switch cacheType { + case .none: + return "none" + case .disk: + return "disk" + case .memory, .all: + // `all` doesn't make much sense, so we treat it as `memory`. + return "memory" + @unknown default: + log.error("Unhandled `SDImageCacheType` value: \(cacheType), returning `none` as fallback. Add the missing case as soon as possible.") + return "none" + } +} + +func imageFormatToMediaType(_ format: SDImageFormat) -> String? { + switch format { + case .undefined: + return nil + case .JPEG: + return "image/jpeg" + case .PNG: + return "image/png" + case .GIF: + return "image/gif" + case .TIFF: + return "image/tiff" + case .webP: + return "image/webp" + case .HEIC: + return "image/heic" + case .HEIF: + return "image/heif" + case .PDF: + return "application/pdf" + case .SVG: + return "image/svg+xml" + default: + // On one hand we could remove this clause and always ensure that we have handled + // all supported formats (by erroring compilation otherwise). + // On the other hand, we do support overriding SDWebImage version, + // so we shouldn't fail to compile on SDWebImage versions with. + return nil + } +} + +/** + Calculates the ideal size that fills in the container size while maintaining the source aspect ratio. + */ +func idealSize(contentPixelSize: CGSize, containerSize: CGSize, scale: Double = 1.0, contentFit: ContentFit) -> CGSize { + switch contentFit { + case .contain: + let aspectRatio = min(containerSize.width / contentPixelSize.width, containerSize.height / contentPixelSize.height) + return contentPixelSize * aspectRatio + case .cover: + let aspectRatio = max(containerSize.width / contentPixelSize.width, containerSize.height / contentPixelSize.height) + return contentPixelSize * aspectRatio + case .fill: + return containerSize + case .scaleDown: + if containerSize.width < contentPixelSize.width / scale || containerSize.height < contentPixelSize.height / scale { + // The container is smaller than the image โ€” scale it down and behave like `contain` + let aspectRatio = min(containerSize.width / contentPixelSize.width, containerSize.height / contentPixelSize.height) + return contentPixelSize * aspectRatio + } else { + // The container is bigger than the image โ€” don't scale it and behave like `none` + return contentPixelSize / scale + } + case .none: + return contentPixelSize / scale + } +} + +/** + Returns a bool whether the image should be downscaled to the given size. + */ +func shouldDownscale(image: UIImage, toSize size: CGSize, scale: Double) -> Bool { + if size.width <= 0 || size.height <= 0 { + // View is invisible, so no reason to keep the image in memory. + // This already ensures that we won't be diving by zero in ratio calculations. + return true + } + if size.width.isInfinite || size.height.isInfinite { + // Keep the image unscaled for infinite sizes. + return false + } + let imageSize = image.size * image.scale + return imageSize.width > (size.width * scale) && imageSize.height > (size.height * scale) +} + +/** + Resizes a static image to fit in the given size and scale. + */ +func resize(image: UIImage, toSize size: CGSize, scale: Double) -> UIImage { + let format = UIGraphicsImageRendererFormat() + format.scale = scale + + return UIGraphicsImageRenderer(size: size, format: format).image { _ in + image.draw(in: CGRect(origin: .zero, size: size)) + } +} + +/** + The image source that fits best into the given size, that is the one with the closest number of pixels. + May be `nil` if there are no sources available or the size is zero. + */ +func getBestSource(from sources: [ImageSource]?, forSize size: CGSize, scale: Double = 1.0) -> ImageSource? { + guard let sources = sources, !sources.isEmpty else { + return nil + } + if size.width <= 0 || size.height <= 0 { + return nil + } + if sources.count == 1 { + return sources.first + } + var bestSource: ImageSource? + var bestFit = Double.infinity + let targetPixelCount = size.width * size.height * scale * scale + + for source in sources { + let fit = abs(1 - (source.pixelCount / targetPixelCount)) + + if fit < bestFit { + bestSource = source + bestFit = fit + } + } + return bestSource +} + +/** + Creates the cache key filter that returns the specific string. + */ +func createCacheKeyFilter(_ cacheKey: String?) -> SDWebImageCacheKeyFilter? { + guard let cacheKey = cacheKey else { + return nil + } + return SDWebImageCacheKeyFilter { _ in + return cacheKey + } +} + +/** + Creates a default image context based on the source and the cache policy. + */ +func createSDWebImageContext(forSource source: ImageSource, cachePolicy: ImageCachePolicy = .disk, useAppleWebpCodec: Bool = true) -> SDWebImageContext { + var context = SDWebImageContext() + + // Modify URL request to add headers. + if let headers = source.headers { + context[.downloadRequestModifier] = SDWebImageDownloaderRequestModifier(headers: headers) + } + + // Allow for custom cache key. If not specified in the source, its uri is used as the key. + context[.cacheKeyFilter] = createCacheKeyFilter(source.cacheKey) + + // Tell SDWebImage to use our own class for animated formats, + // which has better compatibility with the UIImage and fixes issues with the image duration. + context[.animatedImageClass] = AnimatedImage.self + + // Passing useAppleWebpCodec into WebPCoder + context[.imageDecodeOptions] = [ + imageCoderOptionUseAppleWebpCodec: useAppleWebpCodec + ] + + // Assets from the bundler have `scale` prop which needs to be passed to the context, + // otherwise they would be saved in cache with scale = 1.0 which may result in + // incorrectly rendered images for resize modes that don't scale (`center` and `repeat`). + context[.imageScaleFactor] = source.scale + + let sdCacheType = cachePolicy.toSdCacheType().rawValue + context[.queryCacheType] = sdCacheType + context[.storeCacheType] = sdCacheType + + if source.cacheOriginalImage { + context[.originalQueryCacheType] = sdCacheType + context[.originalStoreCacheType] = sdCacheType + } else { + context[.originalQueryCacheType] = SDImageCacheType.none.rawValue + context[.originalStoreCacheType] = SDImageCacheType.none.rawValue + } + + // Some loaders (e.g. blurhash) may need access to the source. + context[ImageView.contextSourceKey] = source + + return context +} + +extension CGSize { + /** + Multiplies a size with a scalar. + */ + static func * (size: CGSize, scalar: Double) -> CGSize { + return CGSize(width: size.width * scalar, height: size.height * scalar) + } + + /** + Divides a size with a scalar. + */ + static func / (size: CGSize, scalar: Double) -> CGSize { + return CGSize(width: size.width / scalar, height: size.height / scalar) + } + + /** + Returns a new CGSize with width and height rounded to an integral value using the specified rounding rule. + */ + func rounded(_ rule: FloatingPointRoundingRule) -> CGSize { + return CGSize(width: width.rounded(rule), height: height.rounded(rule)) + } +} + +func makeNSError(description: String) -> NSError { + let userInfo = [NSLocalizedDescriptionKey: description] + return NSError(domain: "expo.modules.image", code: 0, userInfo: userInfo) +} + +// MARK: - Async helpers +// TODO: Add helpers like these to the modules core eventually + +/** + Asynchronously maps the given sequence (sequentially). + */ +func asyncMap( + _ items: ItemsType, + _ transform: (ItemsType.Element) async throws -> ResultType +) async rethrows -> [ResultType] { + var values = [ResultType]() + + for item in items { + try await values.append(transform(item)) + } + return values +} + +/** + Concurrently maps the given sequence. + */ +func concurrentMap( + _ items: ItemsType, + _ transform: @escaping (ItemsType.Element) async throws -> ResultType +) async rethrows -> [ResultType] { + let tasks = items.map { item in + Task { + try await transform(item) + } + } + return try await asyncMap(tasks) { task in + try await task.value + } +} diff --git a/node_modules/expo-image/ios/ImageView.swift b/node_modules/expo-image/ios/ImageView.swift new file mode 100644 index 00000000..c33ec335 --- /dev/null +++ b/node_modules/expo-image/ios/ImageView.swift @@ -0,0 +1,542 @@ +// Copyright 2022-present 650 Industries. All rights reserved. + +import SDWebImage +import ExpoModulesCore +#if !os(tvOS) +import VisionKit +#endif + +typealias SDWebImageContext = [SDWebImageContextOption: Any] + +// swiftlint:disable:next type_body_length +public final class ImageView: ExpoView { + static let contextSourceKey = SDWebImageContextOption(rawValue: "source") + static let screenScaleKey = SDWebImageContextOption(rawValue: "screenScale") + static let contentFitKey = SDWebImageContextOption(rawValue: "contentFit") + static let frameSizeKey = SDWebImageContextOption(rawValue: "frameSize") + + let sdImageView = SDAnimatedImageView(frame: .zero) + + // Custom image manager doesn't use shared loaders managers by default, + // so make sure it is provided here. + let imageManager = SDWebImageManager( + cache: SDImageCache.shared, + loader: SDImageLoadersManager.shared + ) + + var loadingOptions: SDWebImageOptions = [ + .retryFailed, // Don't blacklist URLs that failed downloading + .handleCookies, // Handle cookies stored in the shared `HTTPCookieStore` + // Images from cache are `AnimatedImage`s. BlurRadius is done via a SDImageBlurTransformer + // so this flag needs to be enabled. Beware most transformers cannot manage animated images. + .transformAnimatedImage + ] + + /** + An array of sources from which the view will asynchronously load one of them that fits best into the view bounds. + */ + var sources: [ImageSource]? + + /** + An image that has been loaded from one of the `sources` or set by the shared ref to an image. + */ + var sourceImage: UIImage? + + var pendingOperation: SDWebImageCombinedOperation? + + var contentFit: ContentFit = .cover + + var contentPosition: ContentPosition = .center + + var transition: ImageTransition? + + var blurRadius: CGFloat = 0.0 + + var imageTintColor: UIColor? + + var cachePolicy: ImageCachePolicy = .disk + + var allowDownscaling: Bool = true + + var lockResource: Bool = false + + var enforceEarlyResizing: Bool = false + + var recyclingKey: String? { + didSet { + if oldValue != nil && recyclingKey != oldValue { + sdImageView.image = nil + } + } + } + + var autoplay: Bool = true + + var useAppleWebpCodec: Bool = true + + /** + The ideal image size that fills in the container size while maintaining the source aspect ratio. + */ + var imageIdealSize: CGSize = .zero + + // MARK: - Events + + let onLoadStart = EventDispatcher() + + let onProgress = EventDispatcher() + + let onError = EventDispatcher() + + let onLoad = EventDispatcher() + + let onDisplay = EventDispatcher() + + // MARK: - View + + public override var bounds: CGRect { + didSet { + // Reload the image when the bounds size has changed and is not empty. + if oldValue.size != bounds.size && bounds.size != .zero { + reload() + } + } + } + + public required init(appContext: AppContext? = nil) { + super.init(appContext: appContext) + + clipsToBounds = true + sdImageView.contentMode = contentFit.toContentMode() + sdImageView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + sdImageView.layer.masksToBounds = false + + // Apply trilinear filtering to smooth out mis-sized images. + sdImageView.layer.magnificationFilter = .trilinear + sdImageView.layer.minificationFilter = .trilinear + + addSubview(sdImageView) + } + + deinit { + // Cancel pending requests when the view is deallocated. + cancelPendingOperation() + } + + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { + // The mask layer we adjusted would be invaliated from `RCTViewComponentView.traitCollectionDidChange`. + // After that we have to recalculate the mask layer in `applyContentPosition`. + applyContentPosition(contentSize: imageIdealSize, containerSize: frame.size) + } + } + + // MARK: - Implementation + + func reload(force: Bool = false) { + if lockResource && !force { + return + } + if isViewEmpty { + displayPlaceholderIfNecessary() + } + guard let source = bestSource else { + displayPlaceholderIfNecessary() + return + } + if sdImageView.image == nil { + sdImageView.contentMode = contentFit.toContentMode() + } + var context = createSDWebImageContext(forSource: source, cachePolicy: cachePolicy, useAppleWebpCodec: useAppleWebpCodec) + + // Cancel currently running load requests. + cancelPendingOperation() + + if blurRadius > 0 { + context[.imageTransformer] = createTransformPipeline() + } + + // It seems that `UIImageView` can't tint some vector graphics. If the `tintColor` prop is specified, + // we tell the SVG coder to decode to a bitmap instead. This will become useless when we switch to SVGNative coder. + let shouldEarlyResize = imageTintColor != nil || enforceEarlyResizing || source.isPhotoLibraryAsset + if shouldEarlyResize { + context[.imagePreserveAspectRatio] = true + context[.imageThumbnailPixelSize] = CGSize( + width: sdImageView.bounds.size.width * screenScale, + height: sdImageView.bounds.size.height * screenScale + ) + } + + // Some loaders (e.g. PhotoLibraryAssetLoader) may need to know the screen scale. + context[ImageView.screenScaleKey] = screenScale + context[ImageView.frameSizeKey] = frame.size + context[ImageView.contentFitKey] = contentFit + + // Do it here so we don't waste resources trying to fetch from a remote URL + if maybeRenderLocalAsset(from: source) { + return + } + + onLoadStart([:]) + + pendingOperation = imageManager.loadImage( + with: source.uri, + options: loadingOptions, + context: context, + progress: imageLoadProgress(_:_:_:), + completed: imageLoadCompleted(_:_:_:_:_:_:) + ) + } + + // MARK: - Loading + + private func imageLoadProgress(_ receivedSize: Int, _ expectedSize: Int, _ imageUrl: URL?) { + // Don't send the event when the expected size is unknown (it's usually -1 or 0 when called for the first time). + if expectedSize <= 0 { + return + } + + // Photos library requester emits the progress as a double `0...1` that we map to `0...100` int in `PhotosLoader`. + // When that loader is used, we don't have any information about the sizes in bytes, so we only send the `progress` param. + let isPhotoLibraryAsset = isPhotoLibraryAssetUrl(imageUrl) + + onProgress([ + "loaded": isPhotoLibraryAsset ? nil : receivedSize, + "total": isPhotoLibraryAsset ? nil : expectedSize, + "progress": Double(receivedSize) / Double(expectedSize) + ]) + } + + // swiftlint:disable:next function_parameter_count + private func imageLoadCompleted( + _ image: UIImage?, + _ data: Data?, + _ error: Error?, + _ cacheType: SDImageCacheType, + _ finished: Bool, + _ imageUrl: URL? + ) { + if let error = error { + let code = (error as NSError).code + + // SDWebImage throws an error when loading operation is canceled (interrupted) by another load request. + // We do want to ignore that one and wait for the new request to load. + if code != SDWebImageError.cancelled.rawValue { + onError(["error": error.localizedDescription]) + } + return + } + guard finished else { + log.debug("Loading the image has been canceled") + return + } + + if let image { + onLoad([ + "cacheType": cacheTypeToString(cacheType), + "source": [ + "url": imageUrl?.absoluteString, + "width": image.size.width, + "height": image.size.height, + "mediaType": imageFormatToMediaType(image.sd_imageFormat), + "isAnimated": image.sd_isAnimated + ] + ]) + + let scale = window?.screen.scale ?? UIScreen.main.scale + imageIdealSize = idealSize( + contentPixelSize: image.size * image.scale, + containerSize: frame.size, + scale: scale, + contentFit: contentFit + ).rounded(.up) + + let image = processImage(image, idealSize: imageIdealSize, scale: scale) + applyContentPosition(contentSize: imageIdealSize, containerSize: frame.size) + renderSourceImage(image) + } else { + displayPlaceholderIfNecessary() + } + } + + private func maybeRenderLocalAsset(from source: ImageSource) -> Bool { + let path: String? = { + // .path() on iOS 16 would remove the leading slash, but it doesn't on tvOS 16 ๐Ÿ™ƒ + // It also crashes with EXC_BREAKPOINT when parsing data:image uris + // manually drop the leading slash below iOS 16 + if let path = source.uri?.path { + return String(path.dropFirst()) + } + return nil + }() + + if let path, !path.isEmpty, let local = UIImage(named: path) { + renderSourceImage(local) + return true + } + + return false + } + + // MARK: - Placeholder + + /** + A list of sources that the placeholder can be loaded from. + */ + var placeholderSources: [ImageSource] = [] { + didSet { + loadPlaceholderIfNecessary() + } + } + + /** + A placeholder image to use when the proper image is unset. + */ + var placeholderImage: UIImage? + + /** + Content fit for the placeholder. `scale-down` seems to be the best choice for spinners + and that the placeholders are usually smaller than the proper image, but it doesn't + apply to blurhash that by default could use the same fitting as the proper image. + */ + var placeholderContentFit: ContentFit = .scaleDown + + /** + Same as `bestSource`, but for placeholders. + */ + var bestPlaceholder: ImageSource? { + return getBestSource(from: placeholderSources, forSize: bounds.size, scale: screenScale) ?? placeholderSources.first + } + + /** + A bool value whether the placeholder can be displayed, i.e. nothing has been displayed yet or the sources are unset. + */ + var canDisplayPlaceholder: Bool { + return isViewEmpty || (!hasAnySource && sourceImage == nil) + } + + /** + Loads a placeholder from the best source provided in `placeholder` prop. + A placeholder should be a local asset to have more time to show before the proper image is loaded, + but remote assets are also supported โ€“ for the bundler and to cache them on the disk to load faster next time. + - Note: Placeholders are not being resized nor transformed, so try to keep them small. + */ + func loadPlaceholderIfNecessary() { + // Exit early if placeholder is not set or there is already an image attached to the view. + // The placeholder is only used until the first image is loaded. + guard canDisplayPlaceholder, let placeholder = bestPlaceholder else { + return + } + + // Cache placeholders on the disk. Should we let the user choose whether + // to cache them or apply the same policy as with the proper image? + // Basically they are also cached in memory as the `placeholderImage` property, + // so just `disk` policy sounds like a good idea. + var context = createSDWebImageContext(forSource: placeholder, cachePolicy: .disk, useAppleWebpCodec: useAppleWebpCodec) + + let isPlaceholderHash = placeholder.isBlurhash || placeholder.isThumbhash + + imageManager.loadImage(with: placeholder.uri, context: context, progress: nil) { [weak self] placeholder, _, _, _, finished, _ in + guard let self, let placeholder, finished else { + return + } + self.placeholderImage = placeholder + self.placeholderContentFit = isPlaceholderHash ? self.contentFit : self.placeholderContentFit + self.displayPlaceholderIfNecessary() + } + } + + /** + Displays a placeholder if necessary โ€“ the placeholder can only be displayed when no image has been displayed yet or the sources are unset. + */ + private func displayPlaceholderIfNecessary() { + guard canDisplayPlaceholder, let placeholder = placeholderImage else { + return + } + setImage(placeholder, contentFit: placeholderContentFit, isPlaceholder: true) + } + + // MARK: - Processing + + private func createTransformPipeline() -> SDImagePipelineTransformer? { + let transformers: [SDImageTransformer] = [ + SDImageBlurTransformer(radius: blurRadius) + ] + return SDImagePipelineTransformer(transformers: transformers) + } + + private func processImage(_ image: UIImage?, idealSize: CGSize, scale: Double) -> UIImage? { + guard let image = image, !bounds.isEmpty else { + return nil + } + sdImageView.animationTransformer = nil + // Downscale the image only when necessary + if allowDownscaling && shouldDownscale(image: image, toSize: idealSize, scale: scale) { + if image.sd_isAnimated { + let size = idealSize * scale + sdImageView.animationTransformer = SDImageResizingTransformer(size: size, scaleMode: .fill) + return image + } + return resize(image: image, toSize: idealSize, scale: scale) + } + return image + } + + // MARK: - Rendering + + /** + Moves the layer on which the image is rendered to respect the `contentPosition` prop. + */ + private func applyContentPosition(contentSize: CGSize, containerSize: CGSize) { + let offset = contentPosition.offset(contentSize: contentSize, containerSize: containerSize) + if sdImageView.layer.mask != nil { + // In New Architecture mode, React Native adds a mask layer to image subviews. + // When moving the layer frame, we must move the mask layer with a compensation value. + // This prevents the layer from being cropped. + // See https://github.com/expo/expo/issues/34201 + // and https://github.com/facebook/react-native/blob/c72d4c5ee97/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm#L1066-L1076 + CATransaction.begin() + CATransaction.setDisableActions(true) + sdImageView.layer.frame.origin = offset + sdImageView.layer.mask?.frame.origin = CGPoint(x: -offset.x, y: -offset.y) + CATransaction.commit() + } else { + sdImageView.layer.frame.origin = offset + } + } + + internal func renderSourceImage(_ image: UIImage?) { + // Update the source image before it gets rendered or transitioned to. + sourceImage = image + + if let transition = transition, transition.duration > 0 { + let options = transition.toAnimationOptions() + let seconds = transition.duration / 1000 + + UIView.transition(with: sdImageView, duration: seconds, options: options) { [weak self] in + if let self { + self.setImage(image, contentFit: self.contentFit, isPlaceholder: false) + } + } + } else { + setImage(image, contentFit: contentFit, isPlaceholder: false) + } + } + + private func setImage(_ image: UIImage?, contentFit: ContentFit, isPlaceholder: Bool) { + sdImageView.contentMode = contentFit.toContentMode() + + if isPlaceholder { + sdImageView.autoPlayAnimatedImage = true + } else { + sdImageView.autoPlayAnimatedImage = autoplay + } + + if let imageTintColor, !isPlaceholder { + sdImageView.tintColor = imageTintColor + sdImageView.image = image?.withRenderingMode(.alwaysTemplate) + } else { + sdImageView.tintColor = nil + sdImageView.image = image + } + + if !isPlaceholder { + onDisplay() + } + +#if !os(tvOS) + if enableLiveTextInteraction { + analyzeImage() + } +#endif + } + + // MARK: - Helpers + + func cancelPendingOperation() { + pendingOperation?.cancel() + pendingOperation = nil + } + + /** + A scale of the screen where the view is presented, + or the main scale if the view is not mounted yet. + */ + var screenScale: Double { + return window?.screen.scale as? Double ?? UIScreen.main.scale + } + + /** + The image source that fits best into the view bounds. + */ + var bestSource: ImageSource? { + return getBestSource(from: sources, forSize: bounds.size, scale: screenScale) + } + + /** + A bool value whether the image view doesn't render any image. + */ + var isViewEmpty: Bool { + return sdImageView.image == nil + } + + /** + A bool value whether there is any source to load from. + */ + var hasAnySource: Bool { + return sources?.isEmpty == false + } + + // MARK: - Live Text Interaction +#if !os(tvOS) + @available(iOS 16.0, macCatalyst 17.0, *) + static let imageAnalyzer = ImageAnalyzer.isSupported ? ImageAnalyzer() : nil + + var enableLiveTextInteraction: Bool = false { + didSet { + guard #available(iOS 16.0, macCatalyst 17.0, *), oldValue != enableLiveTextInteraction, ImageAnalyzer.isSupported else { + return + } + if enableLiveTextInteraction { + let imageAnalysisInteraction = ImageAnalysisInteraction() + sdImageView.addInteraction(imageAnalysisInteraction) + } else if let interaction = findImageAnalysisInteraction() { + sdImageView.removeInteraction(interaction) + } + } + } + + private func analyzeImage() { + guard #available(iOS 16.0, macCatalyst 17.0, *), ImageAnalyzer.isSupported, let image = sdImageView.image else { + return + } + + Task { + guard let imageAnalyzer = Self.imageAnalyzer, let imageAnalysisInteraction = findImageAnalysisInteraction() else { + return + } + let configuration = ImageAnalyzer.Configuration([.text, .machineReadableCode]) + + do { + let imageAnalysis = try await imageAnalyzer.analyze(image, configuration: configuration) + + // Make sure the image haven't changed in the meantime. + if image == sdImageView.image { + imageAnalysisInteraction.analysis = imageAnalysis + imageAnalysisInteraction.preferredInteractionTypes = .automatic + } + } catch { + log.error(error) + } + } + } + + @available(iOS 16.0, macCatalyst 17.0, *) + private func findImageAnalysisInteraction() -> ImageAnalysisInteraction? { + let interaction = sdImageView.interactions.first { + return $0 is ImageAnalysisInteraction + } + return interaction as? ImageAnalysisInteraction + } +#endif +} diff --git a/node_modules/expo-image/ios/Loaders/PhotoLibraryAssetLoader.swift b/node_modules/expo-image/ios/Loaders/PhotoLibraryAssetLoader.swift new file mode 100644 index 00000000..2596fa51 --- /dev/null +++ b/node_modules/expo-image/ios/Loaders/PhotoLibraryAssetLoader.swift @@ -0,0 +1,153 @@ +import Photos +import Dispatch +import SDWebImage +import ExpoModulesCore + +/** + A custom loader for assets from the Photo Library. It handles all urls with the `ph` scheme. + */ +final class PhotoLibraryAssetLoader: NSObject, SDImageLoader { + // MARK: - SDImageLoader + + func canRequestImage(for url: URL?) -> Bool { + return isPhotoLibraryAssetUrl(url) + } + + func requestImage( + with url: URL?, + options: SDWebImageOptions = [], + context: SDWebImageContext?, + progress progressBlock: SDImageLoaderProgressBlock?, + completed completedBlock: SDImageLoaderCompletedBlock? = nil + ) -> SDWebImageOperation? { + guard isPhotoLibraryStatusAuthorized() else { + let error = makeNSError(description: "Unauthorized access to the Photo Library") + completedBlock?(nil, nil, error, false) + return nil + } + let operation = PhotoLibraryAssetLoaderOperation() + + DispatchQueue.global(qos: .userInitiated).async { + guard let url = url, let assetLocalIdentifier = assetLocalIdentifier(fromUrl: url) else { + let error = makeNSError(description: "Unable to obtain the asset identifier from the url: '\(String(describing: url?.absoluteString))'") + completedBlock?(nil, nil, error, false) + return + } + guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [assetLocalIdentifier], options: .none).firstObject else { + let error = makeNSError(description: "Asset with identifier '\(assetLocalIdentifier)' not found in the Photo Library") + completedBlock?(nil, nil, error, false) + return + } + operation.requestId = requestAsset( + asset, + url: url, + context: context, + progressBlock: progressBlock, + completedBlock: completedBlock + ) + } + return operation + } + + func shouldBlockFailedURL(with url: URL, error: Error) -> Bool { + // The lack of permission is one of the reasons of failed request, + // but in that single case we don't want to blacklist the url as + // the permission might be granted later and then the retry should be possible. + return isPhotoLibraryStatusAuthorized() + } +} + +/** + Returns a bool value whether the given url references the Photo Library asset. + */ +internal func isPhotoLibraryAssetUrl(_ url: URL?) -> Bool { + return url?.scheme == "ph" +} + +/** + Returns the local identifier of the asset from the given `ph://` url. + These urls have the form of "ph://26687849-33F9-4402-8EC0-A622CD011D70", + where the asset local identifier is used as the host part. + */ +private func assetLocalIdentifier(fromUrl url: URL) -> String? { + return url.host +} + +/** + Checks whether the app is authorized to read the Photo Library. + */ +private func isPhotoLibraryStatusAuthorized() -> Bool { + let status = PHPhotoLibrary.authorizationStatus(for: .readWrite) + return status == .authorized || status == .limited +} + +/** + Requests the image of the given asset object and returns the request identifier. + */ +private func requestAsset( + _ asset: PHAsset, + url: URL, + context: SDWebImageContext?, + progressBlock: SDImageLoaderProgressBlock?, + completedBlock: SDImageLoaderCompletedBlock? +) -> PHImageRequestID { + let options = PHImageRequestOptions() + options.isSynchronous = false + options.version = .current + options.deliveryMode = .highQualityFormat + options.resizeMode = .fast + options.normalizedCropRect = .zero + options.isNetworkAccessAllowed = true + + if let progressBlock = progressBlock { + options.progressHandler = { progress, _, _, _ in + // The `progress` is a double from 0.0 to 1.0, but the loader needs integers so we map it to 0...100 range + let progressPercentage = Int((progress * 100.0).rounded()) + progressBlock(progressPercentage, 100, url) + } + } + + var targetSize = PHImageManagerMaximumSize + + // We compute the minimal size required to display the image to avoid having to downsample it later + if let scale = context?[ImageView.screenScaleKey] as? Double, + let containerSize = context?[ImageView.frameSizeKey] as? CGSize, + let contentFit = context?[ImageView.contentFitKey] as? ContentFit { + targetSize = idealSize( + contentPixelSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight), + containerSize: containerSize, + scale: scale, + contentFit: contentFit + ).rounded(.up) * scale + } + + return PHImageManager.default().requestImage( + for: asset, + targetSize: targetSize, + contentMode: .aspectFit, + options: options, + resultHandler: { image, info in + // This value can be `true` only when network access is allowed and the photo is stored in the iCloud. + let isDegraded: Bool = info?[PHImageResultIsDegradedKey] as? Bool ?? false + + completedBlock?(image, nil, nil, !isDegraded) + } + ) +} + +/** + Loader operation specialized for the Photo Library by keeping the request identifier. + */ +private class PhotoLibraryAssetLoaderOperation: NSObject, SDWebImageOperation { + var canceled: Bool = false + var requestId: PHImageRequestID? + + // MARK: - SDWebImageOperation + + func cancel() { + if let requestId = requestId { + PHImageManager.default().cancelImageRequest(requestId) + } + canceled = true + } +} diff --git a/node_modules/expo-image/ios/Tests/ImageResizingSpec.swift b/node_modules/expo-image/ios/Tests/ImageResizingSpec.swift new file mode 100644 index 00000000..e6765d4c --- /dev/null +++ b/node_modules/expo-image/ios/Tests/ImageResizingSpec.swift @@ -0,0 +1,130 @@ +import ExpoModulesTestCore + +@testable import ExpoModulesCore +@testable import ExpoImage + +class ImageResizingSpec: ExpoSpec { + override class func spec() { + describe("ideal size") { + // For simplicity use the same container size for all tests + let containerSize = CGSize(width: 150, height: 100) + + context("content size is 300x200") { + let contentSize = CGSize(width: 300, height: 200) + let aspectRatio = contentSize.width / contentSize.height + + it("contains") { + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .contain) + expect(size.width) == containerSize.width // 150 + expect(size.height) == containerSize.height // 100 + expect(size.width / size.height) == aspectRatio // 1.5 + } + + it("covers") { + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .cover) + expect(size.width) == containerSize.width // 150 + expect(size.height) == containerSize.height // 100 + expect(size.width / size.height) == aspectRatio // 1.5 + } + + it("fills") { + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .fill) + expect(size.width) == containerSize.width // 150 + expect(size.height) == containerSize.height // 100 + } + + it("scales down") { + // Behaves like 'contain' content fit + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .scaleDown) + expect(size.width) == containerSize.width // 150 + expect(size.height) == containerSize.height // 100 + expect(size.width / size.height) == aspectRatio // 1.5 + } + + it("doesn't resize") { + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .none) + expect(size.width) == contentSize.width // 300 + expect(size.height) == contentSize.height // 200 + } + } + + context("content size is 168x412") { + let contentSize = CGSize(width: 168, height: 412) + let aspectRatio = contentSize.width / contentSize.height + + it("contains") { + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .contain) + expect(size.width) == (expected: containerSize.height * aspectRatio, delta: 0.0001) // ~40.77 + expect(size.height) == containerSize.height // 100 + expect(size.width / size.height) == (expected: aspectRatio, delta: 0.0001) // ~0.40 + } + + it("covers") { + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .cover) + expect(size.width) == containerSize.width // 150 + expect(size.height) == (expected: containerSize.width / aspectRatio, delta: 0.0001) // ~367.85 + expect(size.width / size.height) == aspectRatio // ~0.40 + } + + it("fills") { + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .fill) + expect(size.width) == containerSize.width // 150 + expect(size.height) == containerSize.height // 100 + } + + it("scales down") { + // Behaves like 'contain' content fit + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .scaleDown) + expect(size.width) == (expected: containerSize.height * aspectRatio, delta: 0.0001) // ~40.77 + expect(size.height) == containerSize.height // 100 + expect(size.width / size.height) == (expected: aspectRatio, delta: 0.0001) // ~0.40 + } + + it("doesn't resize") { + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .none) + expect(size.width) == contentSize.width // 168 + expect(size.height) == contentSize.height // 412 + } + } + + context("content size is 37x21") { + let contentSize = CGSize(width: 37, height: 21) + let aspectRatio = contentSize.width / contentSize.height + + it("contains") { + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .contain) + expect(size.width) == containerSize.width // 150 + expect(size.height) == (expected: containerSize.width / aspectRatio, delta: 0.0001) // ~85.13 + expect(size.width / size.height) == (expected: aspectRatio, delta: 0.0001) // ~1.76 + } + + it("covers") { + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .cover) + expect(size.width) == containerSize.height * aspectRatio // ~176.19 + expect(size.height) == containerSize.height // 100 + expect(size.width / size.height) == aspectRatio // ~1.76 + } + + it("fills") { + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .fill) + expect(size.width) == containerSize.width // 150 + expect(size.height) == containerSize.height // 100 + } + + it("scales down") { + // Behaves like `none` content fit + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .scaleDown) + expect(size.width) == contentSize.width // 37 + expect(size.height) == contentSize.height // 21 + expect(size.width / size.height) == aspectRatio // ~1.76 + } + + it("doesn't resize") { + let size = idealSize(contentPixelSize: contentSize, containerSize: containerSize, contentFit: .none) + expect(size.width) == contentSize.width // 37 + expect(size.height) == contentSize.height // 21 + } + } + } + } +} diff --git a/node_modules/expo-image/ios/Thumbhash.swift b/node_modules/expo-image/ios/Thumbhash.swift new file mode 100644 index 00000000..c2da2b7d --- /dev/null +++ b/node_modules/expo-image/ios/Thumbhash.swift @@ -0,0 +1,550 @@ +import Foundation + +// Blurhash implementation thanks to @evanw work +// https://github.com/evanw/thumbhash + +// NOTE: Swift has an exponential-time type checker and compiling very simple +// expressions can easily take many seconds, especially when expressions involve +// numeric type constructors. +// +// This file deliberately breaks compound expressions up into separate variables +// to improve compile time even though this comes at the expense of readability. +// This is a known workaround for this deficiency in the Swift compiler. +// +// The following command is helpful when debugging Swift compile time issues: +// +// swiftc ThumbHash.swift -Xfrontend -debug-time-function-bodies +// +// These optimizations brought the compile time for this file from around 2.5 +// seconds to around 250ms (10x faster). +// NOTE: Swift's debug-build performance of for-in loops over numeric ranges is +// really awful. Debug builds compile a very generic indexing iterator thing +// that makes many nested calls for every iteration, which makes debug-build +// performance crawl. +// +// This file deliberately avoids for-in loops that loop for more than a few +// times to improve debug-build run time even though this comes at the expense +// of readability. Similarly unsafe pointers are used instead of array getters +// to avoid unnecessary bounds checks, which have extra overhead in debug builds. +// +// These optimizations brought the run time to encode and decode 10 ThumbHashes +// in debug mode from 700ms to 70ms (10x faster). + +// swiftlint:disable all +func rgbaToThumbHash(w: Int, h: Int, rgba: Data) -> Data { + // Encoding an image larger than 100x100 is slow with no benefit + assert(w <= 100 && h <= 100) + assert(rgba.count == w * h * 4) + + // Determine the average color + var avg_r: Float32 = 0 + var avg_g: Float32 = 0 + var avg_b: Float32 = 0 + var avg_a: Float32 = 0 + rgba.withUnsafeBytes { rgba in + var rgba = rgba.baseAddress!.bindMemory(to: UInt8.self, capacity: rgba.count) + let n = w * h + var i = 0 + while i < n { + let alpha = Float32(rgba[3]) / 255 + avg_r += alpha / 255 * Float32(rgba[0]) + avg_g += alpha / 255 * Float32(rgba[1]) + avg_b += alpha / 255 * Float32(rgba[2]) + avg_a += alpha + rgba = rgba.advanced(by: 4) + i += 1 + } + } + if avg_a > 0 { + avg_r /= avg_a + avg_g /= avg_a + avg_b /= avg_a + } + + let hasAlpha = avg_a < Float32(w * h) + let l_limit = hasAlpha ? 5 : 7 // Use fewer luminance bits if there's alpha + let imax_wh = max(w, h) + let iwl_limit = l_limit * w + let ihl_limit = l_limit * h + let fmax_wh = Float32(imax_wh) + let fwl_limit = Float32(iwl_limit) + let fhl_limit = Float32(ihl_limit) + let flx = round(fwl_limit / fmax_wh) + let fly = round(fhl_limit / fmax_wh) + var lx = Int(flx) + var ly = Int(fly) + lx = max(1, lx) + ly = max(1, ly) + var lpqa = [Float32](repeating: 0, count: w * h * 4) + + // Convert the image from RGBA to LPQA (composite atop the average color) + rgba.withUnsafeBytes { rgba in + lpqa.withUnsafeMutableBytes { lpqa in + var rgba = rgba.baseAddress!.bindMemory(to: UInt8.self, capacity: rgba.count) + var lpqa = lpqa.baseAddress!.bindMemory(to: Float32.self, capacity: lpqa.count) + let n = w * h + var i = 0 + while i < n { + let alpha = Float32(rgba[3]) / 255 + let r = avg_r * (1 - alpha) + alpha / 255 * Float32(rgba[0]) + let g = avg_g * (1 - alpha) + alpha / 255 * Float32(rgba[1]) + let b = avg_b * (1 - alpha) + alpha / 255 * Float32(rgba[2]) + lpqa[0] = (r + g + b) / 3 + lpqa[1] = (r + g) / 2 - b + lpqa[2] = r - g + lpqa[3] = alpha + rgba = rgba.advanced(by: 4) + lpqa = lpqa.advanced(by: 4) + i += 1 + } + } + } + + // Encode using the DCT into DC (constant) and normalized AC (varying) terms + let encodeChannel = { (channel: UnsafePointer, nx: Int, ny: Int) -> (Float32, [Float32], Float32) in + var dc: Float32 = 0 + var ac: [Float32] = [] + var scale: Float32 = 0 + var fx = [Float32](repeating: 0, count: w) + fx.withUnsafeMutableBytes { fx in + let fx = fx.baseAddress!.bindMemory(to: Float32.self, capacity: fx.count) + var cy = 0 + while cy < ny { + var cx = 0 + while cx * ny < nx * (ny - cy) { + var ptr = channel + var f: Float32 = 0 + var x = 0 + while x < w { + let fw = Float32(w) + let fxx = Float32(x) + let fcx = Float32(cx) + fx[x] = cos(Float32.pi / fw * fcx * (fxx + 0.5)) + x += 1 + } + var y = 0 + while y < h { + let fh = Float32(h) + let fyy = Float32(y) + let fcy = Float32(cy) + let fy = cos(Float32.pi / fh * fcy * (fyy + 0.5)) + var x = 0 + while x < w { + f += ptr.pointee * fx[x] * fy + x += 1 + ptr = ptr.advanced(by: 4) + } + y += 1 + } + f /= Float32(w * h) + if cx > 0 || cy > 0 { + ac.append(f) + scale = max(scale, abs(f)) + } else { + dc = f + } + cx += 1 + } + cy += 1 + } + } + if scale > 0 { + let n = ac.count + var i = 0 + while i < n { + ac[i] = 0.5 + 0.5 / scale * ac[i] + i += 1 + } + } + return (dc, ac, scale) + } + let ( + (l_dc, l_ac, l_scale), + (p_dc, p_ac, p_scale), + (q_dc, q_ac, q_scale), + (a_dc, a_ac, a_scale) + ) = lpqa.withUnsafeBytes { lpqa in + let lpqa = lpqa.baseAddress!.bindMemory(to: Float32.self, capacity: lpqa.count) + return ( + encodeChannel(lpqa, max(3, lx), max(3, ly)), + encodeChannel(lpqa.advanced(by: 1), 3, 3), + encodeChannel(lpqa.advanced(by: 2), 3, 3), + hasAlpha ? encodeChannel(lpqa.advanced(by: 3), 5, 5) : (1, [], 1) + ) + } + + // Write the constants + let isLandscape = w > h + let fl_dc = round(63.0 * l_dc) + let fp_dc = round(31.5 + 31.5 * p_dc) + let fq_dc = round(31.5 + 31.5 * q_dc) + let fl_scale = round(31.0 * l_scale) + let il_dc = UInt32(fl_dc) + let ip_dc = UInt32(fp_dc) + let iq_dc = UInt32(fq_dc) + let il_scale = UInt32(fl_scale) + let ihasAlpha = UInt32(hasAlpha ? 1 : 0) + let header24 = il_dc | (ip_dc << 6) | (iq_dc << 12) | (il_scale << 18) | (ihasAlpha << 23) + let fp_scale = round(63.0 * p_scale) + let fq_scale = round(63.0 * q_scale) + let ilxy = UInt16(isLandscape ? ly : lx) + let ip_scale = UInt16(fp_scale) + let iq_scale = UInt16(fq_scale) + let iisLandscape = UInt16(isLandscape ? 1 : 0) + let header16 = ilxy | (ip_scale << 3) | (iq_scale << 9) | (iisLandscape << 15) + var hash = Data(capacity: 25) + hash.append(UInt8(header24 & 255)) + hash.append(UInt8((header24 >> 8) & 255)) + hash.append(UInt8(header24 >> 16)) + hash.append(UInt8(header16 & 255)) + hash.append(UInt8(header16 >> 8)) + var isOdd = false + if hasAlpha { + let fa_dc = round(15.0 * a_dc) + let fa_scale = round(15.0 * a_scale) + let ia_dc = UInt8(fa_dc) + let ia_scale = UInt8(fa_scale) + hash.append(ia_dc | (ia_scale << 4)) + } + + // Write the varying factors + for ac in [l_ac, p_ac, q_ac] { + for f in ac { + let f15 = round(15.0 * f) + let i15 = UInt8(f15) + if isOdd { + hash[hash.count - 1] |= i15 << 4 + } else { + hash.append(i15) + } + isOdd = !isOdd + } + } + if hasAlpha { + for f in a_ac { + let f15 = round(15.0 * f) + let i15 = UInt8(f15) + if isOdd { + hash[hash.count - 1] |= i15 << 4 + } else { + hash.append(i15) + } + isOdd = !isOdd + } + } + return hash +} + +func thumbHashToRGBA(hash: Data) -> (Int, Int, Data) { + // Read the constants + let h0 = UInt32(hash[0]) + let h1 = UInt32(hash[1]) + let h2 = UInt32(hash[2]) + let h3 = UInt16(hash[3]) + let h4 = UInt16(hash[4]) + let header24 = h0 | (h1 << 8) | (h2 << 16) + let header16 = h3 | (h4 << 8) + let il_dc = header24 & 63 + let ip_dc = (header24 >> 6) & 63 + let iq_dc = (header24 >> 12) & 63 + var l_dc = Float32(il_dc) + var p_dc = Float32(ip_dc) + var q_dc = Float32(iq_dc) + l_dc = l_dc / 63 + p_dc = p_dc / 31.5 - 1 + q_dc = q_dc / 31.5 - 1 + let il_scale = (header24 >> 18) & 31 + var l_scale = Float32(il_scale) + l_scale = l_scale / 31 + let hasAlpha = (header24 >> 23) != 0 + let ip_scale = (header16 >> 3) & 63 + let iq_scale = (header16 >> 9) & 63 + var p_scale = Float32(ip_scale) + var q_scale = Float32(iq_scale) + p_scale = p_scale / 63 + q_scale = q_scale / 63 + let isLandscape = (header16 >> 15) != 0 + let lx16 = max(3, isLandscape ? hasAlpha ? 5 : 7 : header16 & 7) + let ly16 = max(3, isLandscape ? header16 & 7 : hasAlpha ? 5 : 7) + let lx = Int(lx16) + let ly = Int(ly16) + var a_dc = Float32(1) + var a_scale = Float32(1) + if hasAlpha { + let ia_dc = hash[5] & 15 + let ia_scale = hash[5] >> 4 + a_dc = Float32(ia_dc) + a_scale = Float32(ia_scale) + a_dc /= 15 + a_scale /= 15 + } + + // Read the varying factors (boost saturation by 1.25x to compensate for quantization) + let ac_start = hasAlpha ? 6 : 5 + var ac_index = 0 + let decodeChannel = { (nx: Int, ny: Int, scale: Float32) -> [Float32] in + var ac: [Float32] = [] + for cy in 0 ..< ny { + var cx = cy > 0 ? 0 : 1 + while cx * ny < nx * (ny - cy) { + let iac = (hash[ac_start + (ac_index >> 1)] >> ((ac_index & 1) << 2)) & 15 + var fac = Float32(iac) + fac = (fac / 7.5 - 1) * scale + ac.append(fac) + ac_index += 1 + cx += 1 + } + } + return ac + } + let l_ac = decodeChannel(lx, ly, l_scale) + let p_ac = decodeChannel(3, 3, p_scale * 1.25) + let q_ac = decodeChannel(3, 3, q_scale * 1.25) + let a_ac = hasAlpha ? decodeChannel(5, 5, a_scale) : [] + + // Decode using the DCT into RGB + let ratio = thumbHashToApproximateAspectRatio(hash: hash) + let fw = round(ratio > 1 ? 32 : 32 * ratio) + let fh = round(ratio > 1 ? 32 / ratio : 32) + let w = Int(fw) + let h = Int(fh) + var rgba = Data(count: w * h * 4) + let cx_stop = max(lx, hasAlpha ? 5 : 3) + let cy_stop = max(ly, hasAlpha ? 5 : 3) + var fx = [Float32](repeating: 0, count: cx_stop) + var fy = [Float32](repeating: 0, count: cy_stop) + fx.withUnsafeMutableBytes { fx in + let fx = fx.baseAddress!.bindMemory(to: Float32.self, capacity: fx.count) + fy.withUnsafeMutableBytes { fy in + let fy = fy.baseAddress!.bindMemory(to: Float32.self, capacity: fy.count) + rgba.withUnsafeMutableBytes { rgba in + var rgba = rgba.baseAddress!.bindMemory(to: UInt8.self, capacity: rgba.count) + var y = 0 + while y < h { + var x = 0 + while x < w { + var l = l_dc + var p = p_dc + var q = q_dc + var a = a_dc + + // Precompute the coefficients + var cx = 0 + while cx < cx_stop { + let fw = Float32(w) + let fxx = Float32(x) + let fcx = Float32(cx) + fx[cx] = cos(Float32.pi / fw * (fxx + 0.5) * fcx) + cx += 1 + } + var cy = 0 + while cy < cy_stop { + let fh = Float32(h) + let fyy = Float32(y) + let fcy = Float32(cy) + fy[cy] = cos(Float32.pi / fh * (fyy + 0.5) * fcy) + cy += 1 + } + + // Decode L + var j = 0 + cy = 0 + while cy < ly { + var cx = cy > 0 ? 0 : 1 + let fy2 = fy[cy] * 2 + while cx * ly < lx * (ly - cy) { + l += l_ac[j] * fx[cx] * fy2 + j += 1 + cx += 1 + } + cy += 1 + } + + // Decode P and Q + j = 0 + cy = 0 + while cy < 3 { + var cx = cy > 0 ? 0 : 1 + let fy2 = fy[cy] * 2 + while cx < 3 - cy { + let f = fx[cx] * fy2 + p += p_ac[j] * f + q += q_ac[j] * f + j += 1 + cx += 1 + } + cy += 1 + } + + // Decode A + if hasAlpha { + j = 0 + cy = 0 + while cy < 5 { + var cx = cy > 0 ? 0 : 1 + let fy2 = fy[cy] * 2 + while cx < 5 - cy { + a += a_ac[j] * fx[cx] * fy2 + j += 1 + cx += 1 + } + cy += 1 + } + } + + // Convert to RGB + var b = l - 2 / 3 * p + var r = (3 * l - b + q) / 2 + var g = r - q + r = max(0, 255 * min(1, r)) + g = max(0, 255 * min(1, g)) + b = max(0, 255 * min(1, b)) + a = max(0, 255 * min(1, a)) + rgba[0] = UInt8(r) + rgba[1] = UInt8(g) + rgba[2] = UInt8(b) + rgba[3] = UInt8(a) + rgba = rgba.advanced(by: 4) + x += 1 + } + y += 1 + } + } + } + } + return (w, h, rgba) +} + +func thumbHashToAverageRGBA(hash: Data) -> (Float32, Float32, Float32, Float32) { + let h0 = UInt32(hash[0]) + let h1 = UInt32(hash[1]) + let h2 = UInt32(hash[2]) + let header = h0 | (h1 << 8) | (h2 << 16) + let il = header & 63 + let ip = (header >> 6) & 63 + let iq = (header >> 12) & 63 + var l = Float32(il) + var p = Float32(ip) + var q = Float32(iq) + l = l / 63 + p = p / 31.5 - 1 + q = q / 31.5 - 1 + let hasAlpha = (header >> 23) != 0 + var a = Float32(1) + if hasAlpha { + let ia = hash[5] & 15 + a = Float32(ia) + a = a / 15 + } + let b = l - 2 / 3 * p + let r = (3 * l - b + q) / 2 + let g = r - q + return ( + max(0, min(1, r)), + max(0, min(1, g)), + max(0, min(1, b)), + a + ) +} + +func thumbHashToApproximateAspectRatio(hash: Data) -> Float32 { + let header = hash[3] + let hasAlpha = (hash[2] & 0x80) != 0 + let isLandscape = (hash[4] & 0x80) != 0 + let lx = isLandscape ? hasAlpha ? 5 : 7 : header & 7 + let ly = isLandscape ? header & 7 : hasAlpha ? 5 : 7 + return Float32(lx) / Float32(ly) +} + +#if os(iOS) || os(tvOS) +import UIKit + +func thumbHash(fromImage: UIImage) -> Data { + let size = fromImage.size + let w = Int(round(100 * size.width / max(size.width, size.height))) + let h = Int(round(100 * size.height / max(size.width, size.height))) + var rgba = Data(count: w * h * 4) + rgba.withUnsafeMutableBytes { rgba in + if + let space = fromImage.cgImage?.colorSpace, + let context = CGContext( + data: rgba.baseAddress, + width: w, + height: h, + bitsPerComponent: 8, + bytesPerRow: w * 4, + space: space, + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue + ) + { + // EXIF orientation only works if you draw the UIImage, not the CGImage + context.concatenate(CGAffineTransform(1, 0, 0, -1, 0, CGFloat(h))) + UIGraphicsPushContext(context) + fromImage.draw(in: CGRect(x: 0, y: 0, width: w, height: h)) + UIGraphicsPopContext() + + // Convert from premultiplied alpha to unpremultiplied alpha + var rgba = rgba.baseAddress!.bindMemory(to: UInt8.self, capacity: rgba.count) + let n = w * h + var i = 0 + while i < n { + let a = UInt16(rgba[3]) + if a > 0 && a < 255 { + var r = UInt16(rgba[0]) + var g = UInt16(rgba[1]) + var b = UInt16(rgba[2]) + r = min(255, r * 255 / a) + g = min(255, g * 255 / a) + b = min(255, b * 255 / a) + rgba[0] = UInt8(r) + rgba[1] = UInt8(g) + rgba[2] = UInt8(b) + } + rgba = rgba.advanced(by: 4) + i += 1 + } + } + } + return rgbaToThumbHash(w: w, h: h, rgba: rgba) +} + +func image(fromThumbhash: Data) -> UIImage { + var (w, h, rgba) = thumbHashToRGBA(hash: fromThumbhash) + rgba.withUnsafeMutableBytes { rgba in + // Convert from unpremultiplied alpha to premultiplied alpha + var rgba = rgba.baseAddress!.bindMemory(to: UInt8.self, capacity: rgba.count) + let n = w * h + var i = 0 + while i < n { + let a = UInt16(rgba[3]) + if a < 255 { + var r = UInt16(rgba[0]) + var g = UInt16(rgba[1]) + var b = UInt16(rgba[2]) + r = min(255, r * a / 255) + g = min(255, g * a / 255) + b = min(255, b * a / 255) + rgba[0] = UInt8(r) + rgba[1] = UInt8(g) + rgba[2] = UInt8(b) + } + rgba = rgba.advanced(by: 4) + i += 1 + } + } + let image = CGImage( + width: w, + height: h, + bitsPerComponent: 8, + bitsPerPixel: 32, + bytesPerRow: w * 4, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.premultipliedLast.rawValue), + provider: CGDataProvider(data: rgba as CFData)!, + decode: nil, + shouldInterpolate: true, + intent: .perceptual + ) + return UIImage(cgImage: image!) +} +#endif diff --git a/node_modules/expo-image/ios/ThumbhashLoader.swift b/node_modules/expo-image/ios/ThumbhashLoader.swift new file mode 100644 index 00000000..2aa6fdfb --- /dev/null +++ b/node_modules/expo-image/ios/ThumbhashLoader.swift @@ -0,0 +1,52 @@ +import SDWebImage +import ExpoModulesCore + +class ThumbhashLoader: NSObject, SDImageLoader { + // MARK: - SDImageLoader + + func canRequestImage(for url: URL?) -> Bool { + return url?.scheme == "thumbhash" + } + + func requestImage( + with url: URL?, + options: SDWebImageOptions = [], + context: [SDWebImageContextOption: Any]?, + progress progressBlock: SDImageLoaderProgressBlock?, + completed completedBlock: SDImageLoaderCompletedBlock? = nil + ) -> SDWebImageOperation? { + guard let url = url else { + let error = makeNSError(description: "URL provided to ThumbhashLoader is missing") + completedBlock?(nil, nil, error, false) + return nil + } + // The URI looks like this: thumbhash:/3OcRJYB4d3h\iIeHeEh3eIhw+j2w + // ThumbHash may include slashes which could break the structure of the URL, so we replace them + // with backslashes on the JS side and revert them back to slashes here, before generating the image. + var thumbhash = url.pathComponents[1].replacingOccurrences(of: "\\", with: "/") + + // Thumbhashes with transparency cause the conversion to data to fail, padding the thumbhash string to correct length fixes that + let remainder = thumbhash.count % 4 + if remainder > 0 { + thumbhash = thumbhash.padding(toLength: thumbhash.count + 4 - remainder, withPad: "=", startingAt: 0) + } + + guard !thumbhash.isEmpty, let thumbhashData = Data(base64Encoded: thumbhash, options: .ignoreUnknownCharacters) else { + let error = makeNSError(description: "URL provided to ThumbhashLoader is invalid") + completedBlock?(nil, nil, error, false) + return nil + } + + DispatchQueue.global(qos: .userInitiated).async { + let image = image(fromThumbhash: thumbhashData) + DispatchQueue.main.async { + completedBlock?(image, nil, nil, true) + } + } + return nil + } + + func shouldBlockFailedURL(with url: URL, error: Error) -> Bool { + return true + } +} diff --git a/node_modules/expo-image/ios/WebPCoder.swift b/node_modules/expo-image/ios/WebPCoder.swift new file mode 100644 index 00000000..e29e93cc --- /dev/null +++ b/node_modules/expo-image/ios/WebPCoder.swift @@ -0,0 +1,73 @@ +// Copyright 2015-present 650 Industries. All rights reserved. + +import SDWebImage +import SDWebImageWebPCoder + +internal let imageCoderOptionUseAppleWebpCodec = SDImageCoderOption(rawValue: "useAppleWebpCodec") + +/** + A composite WebP coder that uses either `SDImageAWebPCoder` or `SDImageWebPCoder` + based on the passed `imageCoderOptionUseAppleWebpCodec` option. + */ +internal final class WebPCoder: NSObject, SDAnimatedImageCoder { + static let shared = WebPCoder() + + private var useAppleWebpCodec: Bool = true + private var instantiatedCoder: SDAnimatedImageCoder? + private var coder: SDAnimatedImageCoder { + if let instantiatedCoder { + return instantiatedCoder + } + return self.useAppleWebpCodec ? SDImageAWebPCoder.shared : SDImageWebPCoder.shared + } + + override init() { + super.init() + } + + // MARK: - SDAnimatedImageCoder implementations + + convenience init(animatedImageData data: Data?, options: [SDImageCoderOption: Any]? = nil) { + self.init() + self.useAppleWebpCodec = options?[imageCoderOptionUseAppleWebpCodec] as? Bool ?? true + self.instantiatedCoder = self.useAppleWebpCodec + ? SDImageAWebPCoder.init(animatedImageData: data, options: options) + : SDImageWebPCoder.init(animatedImageData: data, options: options) + } + + func canDecode(from data: Data?) -> Bool { + return self.coder.canDecode(from: data) + } + + func decodedImage(with data: Data?, options: [SDImageCoderOption: Any]? = nil) -> UIImage? { + return self.coder.decodedImage(with: data, options: options) + } + + func canEncode(to format: SDImageFormat) -> Bool { + return self.coder.canEncode(to: format) + } + + func encodedData(with image: UIImage?, format: SDImageFormat, options: [SDImageCoderOption: Any]? = nil) -> Data? { + return self.coder.encodedData(with: image, format: format, options: options) + } + + var animatedImageData: Data? { + return self.coder.animatedImageData + } + + var animatedImageFrameCount: UInt { + return self.coder.animatedImageFrameCount + } + + var animatedImageLoopCount: UInt { + return self.coder.animatedImageLoopCount + } + + func animatedImageFrame(at index: UInt) -> UIImage? { + return self.coder.animatedImageFrame(at: index) + } + + func animatedImageDuration(at index: UInt) -> TimeInterval { + return self.coder.animatedImageDuration(at: index) + } +} diff --git a/node_modules/expo-image/jest-rsc.config.js b/node_modules/expo-image/jest-rsc.config.js new file mode 100644 index 00000000..7cc66d7a --- /dev/null +++ b/node_modules/expo-image/jest-rsc.config.js @@ -0,0 +1 @@ +module.exports = require('jest-expo/rsc/jest-preset'); diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar new file mode 100644 index 0000000000000000000000000000000000000000..5cd03a9b6498ec8b78fa54f0ac35a7d2ce27f42f GIT binary patch literal 67772 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 GfBg>^6Q1e- literal 0 HcmV?d00001 diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.md5 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.md5 new file mode 100644 index 00000000..330a5992 --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.md5 @@ -0,0 +1 @@ +06714bdcfeed575b7b421f37d7a51e11 \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.sha1 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.sha1 new file mode 100644 index 00000000..3ff4f559 --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.sha1 @@ -0,0 +1 @@ +182ad66f47b9849c807dcaee9dfc890db2f97b79 \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.sha256 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.sha256 new file mode 100644 index 00000000..e8b2a5cd --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.sha256 @@ -0,0 +1 @@ +a826ab41c2b8020ac3b17ef1025d8acbe10a79e58cbd642dd290cb094c06af37 \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.sha512 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.sha512 new file mode 100644 index 00000000..814c62ac --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11-sources.jar.sha512 @@ -0,0 +1 @@ +456d35849ab903ceb2f587d3e17c93c0ec101f891c94e966e9b19c388b1482ec15f587016a544f23ab2ba1f04838772b1c49dc2c3ab5000b204dac2fed260571 \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar new file mode 100644 index 0000000000000000000000000000000000000000..feab3919920c2f77fa446087c8c90995a2661d60 GIT binary patch literal 304446 zcmV)5K*_&QO9KQH000OG0000%0000000IC20000000jU508%b=cyt2*P)h>@6aWAS z2mk;8K>($PU}d%e004gj000vJ002R5WO8q5WKCgiX=Y_}bS`*pY^9T3Z__XohVS<) z4&eqa;=tenRcWU%qG@87m_-rPg9=}O0uLjlLm;*wa37%IN@H4n9%F?- zH4r^{g4RLfodF)<9HgR|6vYwG;k1m2_RjYc9ob~+-}65o9+iF`+ zSx*hQaf0i3n)C5_{6{-<;^Z^u_dS`4Y5djux~|^s>QAXb)lIa1x2~@0?3ew9AHIK< zug<^bD~sdbw0ZOF=GjX!FG;ww_{|nUDKbNV8gZ+OqhyO6KwKs9KF?Ie6Q{;a( zwX`)fH~k9$&c6m2*|<7c7&=@0&ygmEE{3j7Hh(oq!heG_HMTb~HF36gbu#{|DP#W| zxRar~rQKisYRrEPG_`ZJ{j2F>{TrB@sh!LJ0%vb+;o{=(KmR_bKflMxYT-n9b#?(pLh0fT<(AoK1 z{nZa;9`#f1f$JlU+04Si zg4#*hLZ13h_N_3*{<=7r+T;#`^H{Y`JWvcTe@8c`) z;aFzVhS$C;2;5Uqpz5%UA4>_Kv_+#-;N2e|vpoMg-n=vc3JF1(b3sc09H+T^(b5|8D5b);A>TvX^Uy6rHYBPeFrG*r)X z6B;uIq5~ZeLdP&An(dlHq=JY7(B`d&T##f*hX;!i6_9-mii~msl-AhMyAsgyS`|ZR zz4alJ^zwBz?GxWQUGs(&b!kcjIq0)x2fO{mm(@_jcT3lY1C45pTE4qsO zyzBbd-S4y|qs58U6rKvQ56kc?i5W9hGgJ8w@Ar!QvhfIV-%l6Mpbe1OUaJvZX_8v- zB?nYNMHJ6Xr|k%_b>~v2z#SS2Y!eu=JyLc0X4!0z{EOchhGN_78pNgL3vJGXLGd@q z;}(4qlNJEL<6;EJ4tPiWs?{l_gnFy^t%H*c-kRF5x+gbY$o%g?#yPG9u7;43@uuC2n@5b zRO7Xx3*GGHDSwm5tRM=I+o66f+Hx6VZl7BG!^8yIr$DTAYzs;w6pC>SYBShi#6pluGzpYVt?x!FV2X#o zrClh)l7(zgahH>orYO}gc8vuUmKN1!S>NJ5g|d4zhH}61V9p+Hj@MN-7FGf{F2!M% z?KWoNT?{DlU-S&NboWo1ddEe^{?3nlHblGb0O1Rge|7wJVTxEJ>v|n{d5lZ{V{(Vj z@R1Wbc@o+j^Rezw#=i8d27gLgbn^)e4nvJji76?R3*n**CkE-Djk$=)Bkc1!XPO4ivkX^jWC%iTj>Hao4i+ za=D6cQ)}@VV>4U*oG-RK^k;fo+7qRRSu&NHl6V=4)6C(@EKufl0$YBTPv#KvTs{xj zJmqjmG2zI;fy_U$s(B2 zgt+frhT{nQU`h+Hr_hJ=9&ZKtaKJr<<}pCm%WctKNv9XoQ{5t$YUau-WcRs z+Lw`)Q_3ojnryJdKhMhiL?$DRCxfL3CzILm9{8<*lkq#<0i{HEStfCz>aH}C6KR}# zfX01ycrE7IxOUO7Fs^O5W1;PA#Vv-Btp9Fc6e-g)b{o#R)|6qE{9@c_S>#1;dC)R{ zKcF%K`XX7SO0f;%dwMV+F?moX&ASaEaqb6|BrI}8wdLr!^q zVRTXa5Nwfz8V`AsHZdbp%GUw4TwML8;yl;Bs{}+b0Ldu2-sqLiZXZ4rlUph{2u;XxV?gQM3UAQJ>E#Jshhxb zT4=#(xR4lfh0sQHYWxjzxlx-ut#%kO*CJHuY=JNMI|i3ngM9A-u^iQD5=sr)a~5hO zaE<+EkkAGXSZ7QVz*tl@lY6f+9=9PY2FHo%eZs(0;vrKIUCyYX`UG#ll(}MJDpLob zieX6gReI|IeM2N+Q<&P%)Wej8M%xn17k%3m83$i^Sl%J5+TX*b59`1v!Bc(Q5$^uf z0kty&8)^^DH^EMnTbw-?u?guQ0Gvy@I2h_&(Z~~j+8f+4!T=KQ@C~~DCTkveC^<6P zjAx(o#W*9IIPzyRjk{iN!i8y6n`Ct~UYREGqai5E2~Tq}v||WYD&Nl|X4lCYU!_7j z+^ROk_$>&%4&gg8m~Gi~E5_PV@dzU`DX5os_;la0I1l+oWdmf~mJ)6=^%+i+aT<*h`sdO~7Qk5!%>ES;41K4asiSCxK0z z=HSE5fZoCsUle?G&00@sb7BHjep$GSOZ-o~IQJhc79>{)$T{C0Z_j>)(CXkCHbBQ@ zcq4V8FT?28+jb=vBcr-RRP$LGx!E8s7<-(}RKu@U^NsEEoyduV;h|S|1d?k%-`@W| zoMr(Qore69&iEuDG*nuR3G5GvnHwhb1WYb?;xuf;9I|u}f@mSIy*E!n)G?vJ@|N`}i-7FhwCBDvvc3ecd=?MYq^aQMf&%bE?teNuTJml@ZP4 zJ+?y&6TE}0j^EX#i37i9uU5zHGAyRaSasmW>%sLva!-@FLnsyd@C%0W8kQ!M+Xck? z5_9^5ijvHpLPVrh#VIA7XtoOt;H8<2$#>}WyZ0xvEk`V`Wg19!m?1guywjtEGoo*7 zU$WWV(fIz%R{H+@e8)VZG?J~HmG|~_A7P(<9gl3l@onVT5C&(kszhJx8GGWBs`B)S z_CxI8(=WX32`#u2IY$rdlS|Ogm>r!Elf6~{`&fpa>-SdA#1kvm zY_7-R=G^gJd)q3=@XF5D*R03AjKhpa&um`qx97*vO;DK~cww#9w!-~Wt(IH~F}=!g zR7(B|mb+35mBWVGe4V)l*A|*%K?dMZX*1mcs$#_+eR;VQDKiv(uOfQHEevS`$P*@5 zk%=msJo8;qc%Ie*YfFYYDYKtwGC54p7*cO+UZMI;SzJqAc&*i*sOr4TbOLKFI*W5| zE(J1R14su8#Yke6Rx)Je>-sw@Syg#PRdE znWk8m7hu?yKvQ?oDlUNiR|)Af4|$S|Y$tjjB;fVLNVaxe-Ly_B$uO)`QtH*W=m9su zbGY1UU(+ZMK9$BwtD@%MMyQ?g`sO$5Fsb))}fG$w{077@dZ=61jeds45o*aU31I71>-Yx$sw>pjw_Dr;1kKTv}^CkUpkk{De#efFk z8t{Ffz5d1Geim!%bYT)S8zBP9Stvi?A$R$sB}#%kI>YC^OWG|(oYPHxR!+2an9M=& zYN;#9R8U*33*Kkw;5#v|iTFT}gv%6r*ML!76~1G&9`_C+aa;5mM-#p4s>Na>-cvEF zua=W$b(^j~+Tuc$zpoq?Tvx*Clf|dd3@yTOOB2=(BOZ^{*Y!D4Q6xAdpLX0=jmSQ3 zmy+BxCta%I(^|rKgrdfN$vpRsl)Q2&*vOZ#eEkEXeWF>ZyjqeIrcLe%6Kb{F-d5Op z>uD6}22aOvNmyDdxZfn1@y}+R0X2IwvL;%yGQD&CtX{1c9CADNh=QOVh`acjwPhsZ zAQiav15e}&TEP_!@o#t2c{HhRt%-1e1EWlOgmFG`eYQuG*VO&$adE+c%wVG9=TU^?Q&iZ)S~pcvs0E- zJ+m)*gr2;wfn1(KkvxJsoEtuCHlzmq5gG-(Ztcq7AE%Y)sOlV|uh5g#_y@ghP}k)i zxQ7|JR2N7MEp{WJITr)_(vkAsxFv9|0zGe>7V_90tF~IQu=%cV1{+uFEF;*T7x z;PIsHkA>f3JYeAx&HV;hJW=2)#*;$1U<<^&o1Og?=b9i z6IYM_GQ3P;5ns*L0Vf^kdMfdebA|r$^D3}+T&!5+wzaT2WONRqGm1cc5KOlprQ|)# zdBaFIR6;`W`EExEZjgia5yLkcD8&hQ$Cqoxgb59a= z3sXB;Lk9;-J9Fp%%tngSa>#-xyiK&aTIjSeR5>~I7J`O?k@PvCB{4QMaT)>Nc;Hqg zFQnr+-{&y(^@VH^!D*hXLt^Nn5nO|uooU~{+&facD;pI`uP+xr%BIyi~EgK$dO=2 zrZDikk8hDD{CsIN?A0iRao|=IKXsEx02Zir&M=8VIfvai!ml*EZFUd`Q~?JbLE*TxWWU}x?am#>?7Gg~9_9HS=^h06*TAxggAPO1TKLcUa5ezsURiI~SV>ytDw`^!_ z&E6m22lh<>6@`9z1=30~R^VDd(Q}XeO4=jm;m#r^(SX2)F!9B0v*YohrO4j>v<<)W z1ELF)0#tir3aovx!xAzuqAx0h zc^}Y&hxVVdTy*YD{+a|+!0R2}s_VG_(9pHKme5^`tYXShmt1m}1~>2TU}hs~kiQ41 z>ZJ>`_TT+J;>I)iEpzIAxpOZ#Iqm)hAQ-5)1YiP_w|g>NzdyUL0G>0cZE?D`9R-1| zuh&WuQ%~tv4&R8KeFBj=;3S3p&0z{$%|~laqtqMdt13pBaxbisTw+jm$_@%8A3D0s=@)`{*JW@J*g7(HG$9NYJa<&+0$9^MzW3=xbv9m; zR413wCuX*0xR2z@lg$JX@f+WQEj0d0`qHG8=wA*M(nP}(RCFKbhlJsEwl;&Y%@j8J zG@e6K)sk2(HWZX+c1rP)(NB?ucx0`ulVI0?riEcW9!YLjSN-(?*|bHI{7U?4;+C1s zej6jo9D3rws*Ss8E3kn@&cPk4l01f{-X^=>Su>;Wc5Fs5eeJxzxITpx`BE)YLNr#a z^b6)LXvdolFG!tt5cH)a$KFC$7>o-h4^l6T%d@N#j-lBPctOjg#sFemn>o`2NF2g zW90WEga=f9&g9GRJ+YXYDYe{L2k9j3Uc{GVh%O5+V~=)d9Mrda{78D`qG>Z8y;!Q{ zZanL-m|~jTK<>nKu4xK7c?o!wA({y(2?OCPA5oYWRkbhweswwLJ3b-lZ|t>U*Lfur z>*AELh&M4HJH`sN;(*Y*j8F=~xaS6=rFGO#J)4Xfc*{_`6 zKf72RNOzp5a!BonI~|rDA4uyEx9km)=A-XGC3m>zP>bTjDtj;HMX5KZbQ!@0$7s_O zos93+Syz;D;#zI>Jk;fNXMk_HVRqo?ew{bzCyQD9hDND(NFkb9ICe^YjF_3b36P54 zC24?Q(-9r& zc#9W>Ga>IAVbhtK{RS?1T6dkBxt-a|{eCc!oRTJ|^<}u~s>d9Wly8*;sW&OaAX|uj zmA&}{_3qjZV~dnA!rFVG(ip5PrEirZ%AFyo^NO_9|FDERi0SgdDPd!@&n;|&-YCs5 zv^MjIMsFt~yN9M@ANfU+b|3kBI)cxb7I0G*=rA?;CU}$P<2kHd z7;62&#Er)s zbRtZi0a>>m;_=PCIE!~Ects%655ZoHx^)cg4xv1Vq_T=gu`gY;U3KrHQ#RB+Z0v1- zDzHa?yF5?dP{UA`@RtjJ5kql|iUcK3fr1(bP6NH@c|?z85Hrjm3LP~KwaUjoRn@6D z5)w9lv<=|T7~{W5Gp^1q_O|~n#bhc8%FhX)_%g4bC;e1F>;8%&iYWNA&PjPngmEM; zObh_Y$XM4FzwX~y=T;cP?i7iVy^K40+~4c0md3o_#kBpcRQvHov!=_0qy$f2_FPQ+UF(gILxcEu_4bj<5RL1CO{~2wfS`2W=Yo)eeVt88>oR)! z7M4_#?U8v675dA08-8<_W=Ardx8yPesF~#_Xh5c78Ga6>EZ-u-vMZSfU{~7yP8?38 z7Y+VENi~Z`>3@^03*}*3xms3A>9ovR1c>HC#DI5C3^nP&gY8XUbKsHO<%KktrbU1i z_;F_BqrXxfR(2o@M*rQmYbugrKz0^f=)JEIaXo!gy z35ArL`JFiQX5=84lB9*GjcHfPxZTAZO;9vL+tc$ zf-w91FF=pJ@IRL0Q?rn7_C&1w4!RWJ&mzbk9X4M7!dt5g*Mo4O-|srC=R0CY0Y_%wgdGCBbuh$! zVSU0XWIT6Tv*+CYQj~8SNph?=g3YSJQ~;^D&w+0V zsaviaah(8BOL-R#ukh#gilU5hsLRJ!(vm%89Y;q@bxgbWNqdfY>MdVt0KQW%rJ1M~ zX|B+Vgn2o>=BQ{|faEme#!^N8E6>_i=-3<4QDq4WbRRpDb743k_9uqFC{k(W zbm*~6lS>izp6ZaVfEyVyPo__*d3{u7B8Ae#@2a8Ox+;N2o==#GaGu}FB+3Y*^$X!X zkpm#gDLE39^mgd|5A+sqZ%r#Fv081#xV@aNfT)1k%WfK?#jk~aLHAFn6YYI119#Rs z-fK^Ltd+k-c10`G+=X?pjHdle7Vg;8h8`%m#1#(N6;F2R==d&oxK>QI-D`gI%Q8fg zt}XQi0?Qngf0Zjvr_5bBJtj>=onYy|ef>wWA^H0v@%$lLJ}d|b?Y}0Q$lv>1{?+2E znzr+-7|O?jx~QX3KLXUY5{y3wV@{x6&i;&3v@*GZP%kO1i*ZSfk)~9ED-1m@5K`}D z!}4`0SPuV2z+B6~pisCMyR&5WSW|MT+b!I4Dyy!HceCZJN&NfuWIR7;Mi)_Uv8Px6 zymh5wldOJ|mG|)6WSZ$BLnJ2FnRHg06=q>Fgz;3;Gu5K)1wF@-Swi>DN1kxy)5br~!M*#x}lcSzkoT$(?Mi|FrEdUk30|Ag0 z0IgW#4Xja0E)v9iq)pA!GzAWOi(3=dKdiT^`=W+3z<0z7VsV?qg@t7<_2(q5uY{eZ zM5UN4$#1j`r83dBQM+T%xw(JnE(#ZLDal}l@)@P*U$@d?bd0*gP04cfyP3J*8$x(h zs{=WuY*6inh=JoO&1`OrXBUC7ewZ`RY=t=jJl0xT6Q$a$#6OrTl@E4!=wTPz;s*qQ z6|8!!z58WJ#W4r47f?HE->{$Ya>r8gcCeIHkff~g_gd#)q=Kcd^nvOZgx77X{@(n&M>;XHWX+)lNZ2JBzvg}c8vuKsC=X_U!;*2AJdRCW8^azw&WS3-kQ*-m&uaAxT(0= zu%tZC?^dHO(S<|o%9EG*sa=`cc zG}6r2%2+;v`_cE@{f2ulXeaVry-(J}cud4PF>c+R2YQS|3BO%5Y1T2x^nmUS?JUdm zHS!0z#$EhUrO_4NmJ6sdGqgmz5A;CK(~;YCGPkK?Q@PMhoHpX(Z4n2&gDvW3E2+H&(aGB%`N%?bp`|!U zXOlMB;}%1?oi>DPgGqb@Ef(R@5+w#Ctb%kdjR6t-JqOo4_)9P8CWL4fXW|()b%FWQ zDgB4_jimM62#u0aacD2kKkrz=aTtg1hsIyQg&_UMl+W7vKDZnCdbGb`(J6Je>40QH zKZ;Iaf0;C7-yqZ7IZu52X`*Dr3zfkd86SK7!{%{M0}F?c(s%;s$tQaDeH&3MtPr5Vn&bRfGC1Xh~jTU=d~# zAw5fB*16%tle9GJ>chajXfEesTdF8#EoGHHeS4<*R&B z@BzF?yefDDUZ2JW?eM+4Gp-mfcWoH(pa*IU+F|e#BDZnB!@t4)V;Eit-u`_1BiF6| zgkpw&9fn2!3`zd&PSC+Y+~!XZmi>>%e~-kG%IosTf(W1IiM}3a<*Eo_KPjaAPr!U3 zSrvE1$3w+X@I4q-PYY?*#GTu0uOd79$@P5{cLNCdUc@oYt0F17w`}dq+}sY>>*|>K zeSO~{w&3j;$A-_{#(JBVq4N)@P-uto<4Wxcyv`qX1z7j%?l`^;DBSPHuyYx%O85md zompsksy@=UeNyjf##U8}6l9 zA!(B3X=~BM#KgV$8XdMtzrW1L)Lgi$OeGB8oMH8P(#vUiE2oJOspPkW2AFM+k4b!Vg z8;Rb;vz!Rp>(apIWuY3z5y?z9z}U_3FuB7dgB#Yz;1@Hl7y7&@@3nTS{*cO&M;Sqo ziR{KaR}@7$hQv@!J7Z7XrcF@NGIaIb-Mq(DEOSGDs9A2<(cOLK++J-c`r?9y6ZXva z|HfqRH8{h%JK2H{zO~;UP99p>*K~C%c}C1P zeMkU?7L)4J;&@aWD7G!QMv`uv6}BfuZZou(XaSy2=~YAjlQov2?UK4k-;L;(kY;qN z6t&X3UA33j`sh~WKxSWZbizI6>1+8t&j%CEX7uAACEB(^=kjE3+4LQfZr(cTD?(qL zbGG7B3Z6dU2@)i5$P__clIkJCAyCf7w%BjD2Mm0ui_g%s!}|;tXuYp2a(G~!&8Q~) zfK0Y|ma9f_Mj*w4!h%*WH-kmy8Ics})EGP$ZB#%wQ=@{_jgqTF>^Zezz#!W@1E?$l zMRN>j7GJqdIJ)Cva@8r`HNs^sMjEuibxzTToGsp-;etcei5$h!jLc}%u4K%2R5KPa zmNEuqhn`kD!MXY;CQS-3RfPYk9yB39Kv@18CJEcyI@sHp+PMfD+SnKw8e9L@GX9@h zVpRphH!6N0D!x%qyv;3$aluwrTg%-4I?-;*O!zhdXjws(tCxd(7}H!6O}#oipj-8X z8$xXdIRg|Bns43}C*0WmuG_9m52$9o61DLH zy@Yw?Yr!dC?J=NF5F&%Sc(uKFFvgAOUfCD6d)09}uUcvAk#*U_IA}f-$z7~jS0Ga{ z&aS9D#xSHP|2`vq!L&yOyJ~vGAhV}vGDOZxZ&u9qiC@Ikt3rXD`?hcjPn6|haudZntvAs( zVCyBvCW88PiIFs6X|{?l!i)EKb}!RHzE(|4gqw^BC~9bFSg=|% z*Wj~9vZty{zGmB?G(uCykXc0Mv2ytCjj28#Xnu_+%$cAai{8GIRK;=P&rDUh4bdAy z59~2FfWOvoeRok*DKG9C6`e`Kpkt})un!4z_vksE)?yf&ma{%FI04DM8~&VAR!gj( zpoQ?P_M4w8Z0MaFN=++Bg?t^936R;HylglQcu!L^Jrx!9dNwAsw@|ytHQlGTo{!b) zriz)vm*Hih5%!d*D>p)8mQ2Ev`TbU+pw_g2VV9A*%Gn9`9o#WX0ass+`(2i#IJ72* z_ci%k|Ck(57Y)gD4Yoa$i|%EsU;bN9-^w}QpqF9eF5d#~XJbes$NrS*_|*OfTQrZU z`NU~aO;pkY_|cJ>(4zg-@A9!V0Awt6$6W$ryrE6DVSqMEFAs0Mz-9y%)v#(>Ozog# zltRV_(om~#QpG@VdpNxTpYoT1bs9{DYtGF5Uho77Wn7$o4AYVwF3HIJ4i?94bsa8E zy&N!AKG&4EMC$P#mi|r!u2Bif(khDe35sL>-Sxxf2dsIS*a0ZdHw?!2kR_Z-bA)In zqXJFqH^`&#Nk`@Gz3QZ_F`4Uu95(l#S9C}ZdyI_*#Eqn5zl4e*(4wI9q?{=pxa+I* z4X)AKKWSVoG~_3_ut@NgUzqM~O(8iU8v{y(drM;bAmRD?&T*?T@V9qyBL*l2pGbgs zy&;LumH_GjmxxphXT}5NN%PNt+Of+g3aYe&1OX{R1_9yvZ^-@k^hjc2YG!F?`L~4v z5=IinKj(k7cb__AtD}DEYCjBMlPa#WlqjdQ)})QZ%|@E`%i1h%y-dT5I$?3j;e0Wq z)z!AHa_hg4a`Zr*xj70D_YA%QHg=JZ{D>zMzG|AdqG#weE@V(P04}OSaJZ{~<0t7) zm|*B-Fuu(4x@_;tt%F%q(VSDbc5VQq zw*nqm6e6C%JU5becCX%fBGYOSa-C4HCFooZoHBl>WT zjkc1TV6|;V8mvQc)nWo~1Zw?^-gek^khUtC>We8}f08}zB?JDl}&j}y!Rg0+FA)Q-J1EWL%{NL&~fQs$oDNiDgC ze^zFz(XtNW#wz#hJ>V&=OF|P$2Y@L*ZqYKqZ|eak;yUU}PTRqsr>S@)3mowYgAZ>e zOc9-d4+@V-JO~z-gNT@!M?}PKDX!4p7}l(v2c;8mQ6A}9-_U6*o#)9MHZWI8r8GVX zgqWi8(hV;Ul%Zp}5c6pgbmAN$w!4tr3o8`E%iyzJmc?D?3s+$50l97LOS*RWN#w$* zcMtCh4mtzEN9!jpKH$02S~0CFK*`NS>(lO7at497{`@KQ_G70OIV5^>{P-R5aF@C(JcSRVY5 zJSJ;A z8-%-H8SA3>lNNrJhw~12-^q?wa4PB&rEj!O~2iG9Q@$uX=>|wggTCfhBf@V z88BMmoY(O#Kq={kd%Y+O1!Dl5zf-7_6@fF6<#OU6d~~~ixa-gR^4p1K0TuTs{5~;MBb7su>}9mCR~3Ja zqJ23kMtAyXxKeL1!{RPMx_{3?KU+GJfa*Z$UBt7D(Yg)H08OVv4^e7IcAJB1W5sXw;;fF zf+Hz#UN(c+R^G7`6?ZK1nsB1tFuZye5k2~8YpZ5&f4KkTo}#0QF6Nd5LoGc5cM*GB zrr#2#&Rh?x-}Lzs?4{R#F$4#`h~QgLKI$Xv#Hdk&b0A%_$s>E9PUy}*z7{}X>z@9XU&!7 z-KNVOc||AK9*%6d8bV9Q5to*vg=!{pl>NvP-}~He5YcTXn*)djEsGQ-6(Y$YA1R(E zKhGX&iHN&*S4Bb4S!nPE?778)uW@FX?-UDovPU#KM9g&ubpC?AamCs_u-N#HYy8Ap zxb&6em^Yf=0Mm~o)bD5Sfnv@h>Ko?b6}mURU%&h{J3I7cQ1;Jt7;N~|CxIX?&0IWs zMZP#p0#4BtW@;~WhCpeJg5TChJ*IJo#Lusxmpi|$}fzC00-K56IO z?+6$KK4sH@l1ava2ma9@Tu~3@#Ermjp9uJ`{-e}+vnL}@1=c=AgWA)GU4z#VUU*$~ zBCLokow8*QNC5vf%2xdz)vzmfi~V1WC$Oa4Y1etLA53~gTkoB`A5AO7dW9@Fg(Q69 zl%BqLg+y9a1 z-2clg&hYm%UZ7$v|F?#lSs_@{#Gf}ev}(4Xx&l|KBGlh>l}i!(G5BMS-2!u0m(AR@ z8L!2kCAut(-p-<#HZ-O7Nz;~W%r7>6AFg>#uC3$qcY1$ufQ-f<>;FE_N^Mti0JWKb z+CenN>7==$1+`uh=RunY$S+eeE@5mH+cf}O-dML!$Of08-a0zUE(x;fyL}O-a<0&0 z)L%@fVC>-BtR5)32{zS^+Zl(ARxj%ry~xo6@RX>f%}tiXow+0twy=BQ&;rcis|cbn zgl(jnn8y)h&65E2xxD^rBp6yhRgan1IO@mv!-QD-^Gn-IyBRV+qSctCRirY7R$|uH zV^#dGP~2stj}`P+d~wzRybq<-qhHx6=5P+7A3a1B2zvMLK+RnQ_`VEI$9FN_Asyhu zM*qqtJP#)@U+sA`RpeswNIw+a&S&X&g3^5mByN_EyUYJJ;SENPjL}629OJhhm>PdH zB|ZEA0NB4R=4=n2xu0Xz(6(|wz~P9RgCXW)aJh|{;3Q+Ll=tk^=@^0{hE8&tyQLaKCDw9iEY{k0OLd`bFwh(~n?SL-ENbyQtI*HKB&>I+- z+KL=+x6Un4^lojEb45`C`z~wDQY0=pW5r(1S83O5kQvLQnHVD5<~H#j%JzQ%zYowt zueVt{n}>SEq2pf5&;v3(OGI+jC}FC$8EFav*>@c_(eKpUCMZ`OeYn0_`OVWZ*3*xm z_6MYicZZ5L9V{E^S?zVrroos5i2)({5IGChegx}-MZ_gk}dL!E|G}n_Kbc$xTRT!k*Y)kn1PqlH#+WxE7AL0%Fe-MxH{}N9bSrUae zGY|k!1bYf=eIjTh`w{h|u@099W$3)3XAWTd}r^%_$ao$fp5~OT&U=um)#Cb)S89WEh!(D8`TO=_{HeYnys-| zXay;%9lP^L&S&2gq`xfavw5r2t(yGGC|M$-j&al0i#@~;fkV?RkVME`aGPo-kvu}p zd6zdS@=XRP(@gjUs114YwpkieSgwLwMTv2d)Iqj*17mqSm8Q4HFE3fD@D$dTR3W5a zD!}FMPYB$d06L_((wB=@w)OdJAl1igD_LgK`P|T0$K8ij_AuhGGe#f-0=&R6IrhYncgGAIs7xyi42FWPs zyyP%XrPN8@Rl0LW=!_>p^b*zPPczSB zs}n~UC^DstLJn@*S{Eu+zOKG6i*j}a6VCG))Sd%syic`5B;kCP3C7B3dFRyO2IXL+ zdpo?5)mAKDt#6K+U%n0kq5V~iwM1Eb;VEGOWFaI&bs^i#-F3m-f+{dvpN|;g0AoxZ zGH6@OQhh%D3SYm;(OI`IBPpSm$2afItw!GxI5)3ZMd)&o)w_;*A-$d}zG{nAEJ=Ez zXs!AbN^VX~`lgu->7V1wr=S{j`qc%D;vKH4d#Z_M5%nAKA6Yr7x5xd5swUfp`9jNi zwulmWX^JDTp59FgtjaE4Q_nd?Io8tm!&AbGFrIiZtfkZv&XY^pcb#FdFR+m*@HE~&wT!4ORjPToXyLH3Of+i-N29x$v}uFQ33ESVq7jO)*~5<*24JW`G! zh6#*76sg^EVOl?3#4Ht47t4mo~~JTdSSp1* zVdiwN$*kX#oRgjxEsyt`ouANKY*>NVH{Ln`-iMS|Ha3oz9h zc_3oObMZNa^y>?Rdd7`CKZ~TsD1Y5WZ6*aa%zSEkd1khpGnjz{xl5~X#rdOUogYys zaH-f?7zR2FtjF24ZHq@{9j+t$A4%v*G%Il#p>KMmkqw#k7c+%1b7l+#2ZmpgS{O!H z+n29bsapq@Agd${igP6QbMTqTk*kdKfTQD&SkOyNgWwPT81%o(hthl=6{qr(nBt+^ zGJ=n97NN-YsDk>kP6DyLaIHm_bz`WGhi^a;j8cgJv1w%uYbv+JlJ>1MvH-QG#Qi&8 zH0GEYW28KE$pwz#o`cD)X_hffv)1Zg>^vm8 zJ8m1yu_bG6>MMDuy;5!t0MYk}#n#pM{mondaRQG;P*LyG5|Zxt4c zx59RwNyel0h_$i}B>*)*%DupW+;$B$s@l~e&Zotv|Mfyfpnlx8b(a*Z%lMqZctwf+A61_uwT+B>w z*b@_Dd@ryIy@wp1V9F5L1GspD0-rfQ>Vp!3B*Wt`$K9XHmR|v~TP(5z%XDXAGPRgx zKa9o7S)%a~7>880$ zY2jb7y52f)%%9iaAU*|7rlq|fsO@;dao-(`b`oeXdYfvJ^o^l6`$K1o`)`>}c;v4} zCYRlBA@gz9oUVcb=oKBSf2_izeg4zldT4*N!QZ%x2Km3|F7tnJ_fi>I9K&}5+=)cw zZ`_3iD7tbtJudi|wz8_>kni#X0g}OTRriR@JURwg>ObG$o9|ot5m5LdaUh z-Xb7@7Ivz6N0=!{&lH&DApJEEdob=(za&LjsztYo652XQo=(6q6WR@PDR}MmRmG0N zN1dZ^AK2ma#S{_(2xX3G!)~poamq3AWjj1+FDOtthJPP+OqC(lKWmU8bRHK2) zm+L_^QYn(gSo+9F=bAlJ!Lj#aID;uonZ^EdeuM?ha zB>hH}O;g4WZ-G4VVZJ!A?WC1^AeG*oE%s{RbfM!%>CPY>tDyv~>SCRG@PAROs&LiD zpN{-xLSQBnmnpk z{7F7BrJ8WstI)@t-sChS2>@SWO_0Uvn84x~!#1V}+qK=mZnN1lKSBlVw>$?|B~bgx z7+Y=$ovi4e5XdD3ky>-2m!LR%J7sLwzTS3=Z2gqgeZg4B7LU@`L~{(oiP#918eJ~g z@JrwhwVGuDB|fp}KOjBXJ%1|*O2_VMydWIG-g6x7?e+gVyZ3`-;mCj3{qld%UG{%l zW!t~@X8oFiut!K>#QX1X60l>SKZu80?20Sa;)QbzD2}?)$X0uguQnQOMWf44{Z~ev zAZk<=W3RPz-{0Hw+)G_t^m}?eMEt7b(ZkHZSze{(7|tPiUsQ1s$BZ*xo{Z(zNtm#Fq zAD=M$`cYxS1PYqFKjw9Ro*Af*=(&Er0$_u~S-dFlcW)eS+g!F*Enu7S7G+AywW1j?R>+ea_5R8 zlB65rL=H%$v-vJsWL_Yz_t13KWHL-#g-rvvjL*I%+bO0}&uKc~(%Gzfi-Na@mhU_ba3pA*LF$=OM2;iFlW+TM#UYvem$ z;e{%5rNyY@8JaH-$9A!~Y)rCYa2(O!Fg*WCq|*ORq~QK!u|%~IBoB`{7KKWiVxpf0 zn34FmNsluUpWAkKT4m?OSB00BgFok3)7c#GR}MtQyCEIUsZ71w2~lod1Rfs0gcy_M zFLPHqqR~&4DY6VO=7%E%@$b7C;qx!aY{?DTZqRo|lGpe7b`29EZO22%3|N>Sv2dCl z4+$mKI3JAhMj21H6gXSsf&Gawe4#9Lq^d=W^fy3(W5oq?F@2w&dv7_rpeQ$baIY5G z26s{~<7zMA3L=K2Nxu&iahKh2HkoGLbiXU(lM03WA-tOEFt6%W*`3# z63Ovz%=A>2B`^i(@DT%50G5JZlZt-G+sNg|fGI0KmO>Y)Sa3%?X;!&* zOt}`c&Yh>>$2O?R8CH?cH~!^s)1rR8{@m}(x)^=RIrqq-YgvK329zY=i~?G}AG#Lc z7DW~3kU?U!zC3A}NMVJI)5Efu7`}rQjULW}Q- z!)bg9sC9EvFv5zrNg;(ofmjQ-P05|MxW6(QtoE^bAlw(ZOdwUD7BL|-D^n$?zTHFQy!EIiUr3h5F;r4_UOuG=gDNGr8Hs$V=(iIoMUhpsJcW^ zO)RR9~rZpskSD&CTSIgd0W!X_}mxYSb+H&H~L*@>fuwV%QK4*1G2DM!Ho#fI!{5BL^DDrzSltF ze1S$9QJdY+HG|fSj8Yn$Sr+JCn*PF_-ON3cy*x%~4okYnxFE+Qn?5q~Y(v+Qv++BM z`^9DT)J!)?rdQY~0%Z_hB*s)4Z;=5_Fmj|^rbkICC^4h#BdZN8Mg?YZXANRzftosU zP|65-SV}-rIclP^NMY8O;qe50L$BX31KC?MpDz~A%S?@)Bf?DkeSftIoDftQ3l2XLz%MsqTb7thy01Jo)!Ih>X%4o1EDJSf>{hJ`JQ3w zNyE(-Y|7YnTAB?G_y$@f8&N@p|5@ujHMU3o`hU!?oc{qh?*9UAYeP(3@L(k`MaW!< zXB@}vSbQjI7K?+Lq){cfazo-@;Ga`1&lk^pDn~W>qGWrH5QLf&nCelnT3+6{|A+9i z*O$pp{_k5tVIgXl1;;vs-B{kF4J*j&sOpKjO?B$%?zt56dWRBKOo+}wJdXx!J;1E_ zCXZ+xqGbH}qopK=Jg3T2$t=3cO8r6Y=)!y>5o?}C#>s`Kk$IQ?=ylkRWh`M&lpEXi zyAR3jzfeMDJafll*0a}yz@RM)1lT&jqNmy<)OUS)4(Efxuc3k|fK-baB;2Y`!`J?5 zBnny@zn_~7Thx4#wPpVRp3m_z)U6dqM}{VKrqvVyr2($OzypG~cWn@g_3rY$PCVT` zd-%R#v+7b4)yY;1Ai>lrsnd2ut57Ozu|qE2i@;8>0tL?cja)M~T`rXlQRjIgKr4OJ zf`$kC0VS`f&gYew%8G|d$QbuhO5BnxgP4;&p3_vAN)%DgRfGPtB|U@EJ=;R4l`))T zqCTa)P=YKhp^CUNgKGq8(g@=YS220Gb_CU3->V4LATb}2*Z|+X+m?Ii3mcCk;SwK> z5;yzsE3+XKV@N%{rm&TKgUn8-TE4lHFV|7YE^RVKWjz?sCcX+f&j?Q9VjcZtRU*6s znN=|INODMqhPI{$H9E6I&43A2zO=@M(M z8O67W@MjX^BSWs$9pM>==f)L>BL5fY!?}=_8U4-G8{d*W8Kr1GYf>oz9vvApdM|ze zOy*r~nG65tf_Nb(n@0w40JxpR6BDlCm(9K7Mu@3~c+^_7>5f~8q)2iOt@0z9;u%m` zBVQ+Q;-f&mjP*2~D2g7_&I7nYx{!L@SA~iBo*wNUIkJ_IYMjYvGsXoix^`XI+%16{&7(9lN+|u((uY)mpLca5*3Amts+K+LpW*NC_+nm z$(e7;QrmP_Nv`CNN20NKKu}6mD4kWIeJDLlPRZ~L{kh+Ak1zP7)+qNaTdnqFi|FK; z(TzyMf#b)-Hl7wqR^P}db% zGyFTtteiyCt8ssn)d6{YL{7U+S$wROrrP^Ye+Sz+%5(Cs!D)j3gK~5IJKX+vEm;U0 zNc~<(Si^y(owA1)_R|LJVq^ya(jr5?=Dc4{nl!XLr9f$_^o{??C#^;qhRV8Uv&7FM ztILoJROn;4;=F5>laB2+q)hFa@%JQT7xA{!Wn(!w zbOaff%PetSQDE_L_xE_Z|vo;#O#q;^RO-8DjLC0!>9M3w{&+Np{0{s!( zSFyHBAj+R?AfFF6jM)}zbb!!QFv;vo|3ubEs_?ON$xe@%f5g`x=Bm7}Mt+FMq-&+* zzjt+DvQ)boe4(KM#itCQ1v1W9aAc+&9tbGFZ%==Tq>4-bcGt~9 z>v?sA3&nq7+{$ruRJNkZZ{gX%t>&te#gJ4Wf&AH`REV<#$45F>MyB;Wian7og=M=m zTbopAt+e%YdHAc#VT^ffg{-zxTKk|u{+%cF1$yM+ACB>+tJ_JTK7@#l3Uaedy}_DO zQD*xDUf#nncJxw2MjNLNkkt6c3a%bc>w9R6XbOQAA`SR>W3921AFRCff=RNTadr_DsAIR8N+(h$XkBb>Dejhowyj0 zoLbxYO4t6{OtJmllmGfF(gs2qV83rB$(nQ3YZmYYyCc=v9VY zl`4!CO3S6wOcaC!>81M1QsyNjlx=f(BjbF0ikuv zKgFZYEmd%zBOo+@P57Tn`MR)RTnS$^o84i{gKgBSUl>svf`meTgV4W(7XT8MvUp_N z17Ch!=du)>j?)8@-EcUrFuUALFEG4jLN>`oC(V)Ua(bq0)vWK#%6C1%z6LJ-uox+F zAcA6`Q6;J9+d}H?2@C(;;l=nws0O z$oXnxYki->enrL#Qh}8nxMBxYrc1 zJI@4!r$ImV{2z(g{n)U~<`1_-|D0R4|7Sl)8r6?7fd1npdgjV*w(kwgSZ9{mR$p%Z zyqAB;ZdFdNgF3(M|H`gLAde!gtZaBLuMS3ZVO)TVT2l%GcD*@(8En4Gv9?zPnG5rD z_LTo6T&nB-uO^CB=1v3?0QB+HgG06;UE+9FRQaOPjcr?979+}YR>KW+os#u zK4rE6se^6Zk?`IV0mbU&-$eK6MD5?A%W!rH&It!lIS~>$$*RmLsLP}!37^PRVkbq6*_M%dpv#G(`?K1R{-?Ron9ozo`@ znvdq+gg=|{-r?<%giD*@{+3S@u>}5Ul%-j8m03Ii%HOD5N8m9ntF(iygsyzE@_*;-ngOy$^oU7XSulv%L6W$e%sEg zc6KvH3ZgQ`unRQZwrh%Wp*K}p&6F-@jEURmcjN|ceR`d!26JDRZ^hRlx}HXqLuJZq z={uz2n-cOQUH&kP2kZMUhTFa!Xu4XrjqHYP36JKFmX!vSXrr3+)kNd{SVaBY_A#n<%_rII1e?ht$hnxCjNGo(t<8{jFJNJ(}D%F^i`| z{Pk4%b=(v&M8rZPsV^v$T!-93>yHah$hC0x4trP=o$y$zH6k1IZOnoAB*_cR;h!Nr zjtLN7va)ItN?|M3ojJ&?DX78KwcXMWz2?`0<}J{NJ_ZMm5_% znHbe?0`||(0d*=JBvMZ18Yhn-jxs_ugBd}(v3 z;>^Px9rMTRt2(Mcj1ZFHV6OphZ|~;zNrC3($MXk+w|6bzWdPz9Xqin%zjHgR$3?q? zWK4}!OGYPpthKG>T#CwtKb3ATtbgSj?7)SNX|j9kq0=I>z1=wiAH7BDUgy*Dc+>&nG9cx9$T+i#lMv@9l3 zThq)v%$}^ANHpNtJl=hDQC5RrtjvK8QNjlR=*=h$W?i1kcZ0R!G0IUVUh+RKWMBO^ zb85ta?K}K|W1IQB4L|>%`WN$n!~7bWTm4bcT-33hLw55ATa%WAbLYxxdJe1tCFPTh zA6kUPj!~e;)mW<(L#gCW^>o=ZBGE&ZiPkKG;hx}o>PULQGlO3H$Q)4IS9UQ?Y*yZT z-L4D42z>77Nf)0m#J)v7{ZcRiL{*3WX-drH61&;zm)f<~ybs4HzGk9-$ zk;b-1*~2sqr5eHk#`Y5Gbk~^1Ki%@-D&r1DE>+XmwTT4aTNy?4_enGuEA8Uij6^;b zeE@2z&`4|h98gDo)${O1oT?ef={|y1&65nLQ}AbAz0*1Lte}>H_4_Km&xh_?g*$D` z>LIA?SWx1oz1$nk0H!Fs7-d9(61KH9QCniv zE3rW(q&5(g`v9a-V?kBj_}4cEAVu2_j(*0M5EH;=F6n5Wxp#`*qn8J$l| zH+<2T!$gc{xj7=_N)$Zre}D5*QWh0~BuG4<45T-dRecWH=MKhE>b-sv-VT%*HROe2 ziNYNtjn$$=HRvNQV+k3%8B>uP7Zi6x*)#UJZKqA5_M^S>{jGf>$K(d9apBsK^+RnM zy>F09Kw`kM$`9gC=6Lt9>jgSIniN9JDHZke>J|L)h+XT*Q|p=F=c9mMUnRT{dZWej z$8>fDCm9j>p)z!mqA|}2?pK%wLaj7$opcZ(_^cLL*V62f&Owxlo z^wFJ&%_Bd)kl1UA4uX>UWBpewe28Q z0y8Q1=-ccxK!Xe&5EI+Kw9xP>fX8nHp9d=*e712l$TUhypAs&H-0<2VU1^xduN zsB4)}OW#=!bBmNl1^JK~6S(6Rk!g!Xk`puzBxd$`|M5{Q%t1f$c>wXBGVowMpH@`C z_3$3VNl}*NiH#7a13Kx%H zT21%^vpd$#N^U25reaEE6}I}rR!rf;*3?B@o+k2vxo=vVq;qCkbFyWHrOUkQsQ8JJ zKb1xs)m*!M&nK;6?Qfm&F;oHHbr9REmNUxQ{fDWKi{!7r)^%H}1HHnv=e}fu#rvG- zLPpazIu;r70A$BVke^4hB25x_dq@x$Rrgoch^BSS{KZQdwd34}QK4+CG-}moRTV*P z_LPq*#1<8{(p7&KMaAdxR1W^~1sW^`G9)&VQ1^M5hAs z^bX>#>-MByqCEHo?(hXprpHP^iB6-$pa&nA24C3Mk;%ae=vVyI4|u~wlS1c5AE4+v zoCF5+sJO`-dGqQI=`Sftf4zG?J>jPy%M=0iq5U;B+k-T|C$;-@8XDa~R% z$~x>H%wcv>$+?CQwUd6tyJtKK-nVLjP|{>(zgk?Hm8yb|zAXI*(q$G^OrCVg)q| zQ;@(Z6y9;b3na`fnL(K_bq|RnJD&JV>vq_KfOGL`_gbyPtL{Xt;OB z1T+AJ=BKxe#jtrVV$X-tqw1< zgqswN2q?q}oEwt<#wC)QUr?Yho!!!RC6XHRSvxt1vG-!QQELdj&beq3Mx;%1so`g2 z76I!JTH6*O7MhuY&4#g+qqJ6`j<7s>7Ut(r*h2ZL+Wrl@oppP)Lrt*ueN_4O7sn19 zEg*5mwOfHk`3J|s!zUDR>Z3U5k6dAc^vAj8t1&jok-X-PjKPzRSuDji!@b0tPUPE= z5k%NW`T%~!LMHJ(Ql7!Eo69v@p8zIjV6R5tbm=X{&K5Ev==p7jd@QR%Wm)BMN*pNQ zONyvr>jUm0P^L!pT3NuZYPwEZ(aA@}KcK%c)=w9=4P1ufXI~yxrQviY1GJw;8 zlFDG??-Tw{qVYWU8#A0K2>L6P?(J4ho-b%J`&SF62Q%Qlff7|)4_E|K$au_+3o zQ)sG5*lBw=Vf-&38M1l-&Z##l#)!!7KJo9epj65FFs+fi64 z;&{nmrsorUu9vX>9}|xZ0v{k0e>l|r=NxkVPaBFoh9s)rK-B5I4~YUblOR*pp~SRmK6{cRW=sQts8Hxh9ei9| zlWc606KmxQOs{Ybgx%H&=7NoMA< z8cxK$>*dRNR0IYGMc2!FO*dOdR&*i0g_)=N%bv4ySpi}bGV5vS1_Jbp;5h$P6!Bs* z$Ptm1#$cVLuJ4(*uC!~e4E32}HUxF^W|LwkhQefDo)0n5rL_)Z4D>|A)eOw_kQEOc z4W55sQEDlunC{G>_{cS4Fl}Y}UG~s%j5+Mwy?~o^wwEh6cRKbek6_^AUgkK;ZQ*H1c7l`FIR_xkO;K!&t8pf%%xZdKN^wH&trjSXYhV;(Lur3<^dc zI%zN>6j(hlE?=-ul4H@90Im&SNa+55vr9$J~1#a)6VzfQ-^@rnCz;$p=$2suv za9!|hD+xA)LM1K|W@Y2`cddaysFN=E|%36!Ff2s!DhZzEe#jwu`7@74`Rw`}}g_xjmjxOce$z z!ama0-zgv(whA==YL976oq6++H=pfA=kQ8r3E?%ics^===?|5cR_DB)Trn(cFup>W zG?8P!XYYSGXQZV0fmL9Wd(o@+FzZcng@OY2+gE=HM%RXMXF4o%L+n-h#rIzQdNLKZ z!sAt#SXf@r_p>&G{aJ1&zkar^Nmz&I5jfzmL?V84N5ff^aZI1c`HAst;}`T6^XLr6 zrpR(f?r2hzfvNEFHC|I%^-tt($s||UR;Xj@-Ca$1!+2s85L)V=WMph_`Bu(YwTXi> z$MtvkI$TFc(*D5b`phf_H%EiqTxlGX7p(MVqf+R>(Wj z(INv?`~j6=ABkS@T+7{@>qX!NTAq$ViB9c7z4SBf>B)o9w_Nrxit~wy(=oQ|v9Z^e ztGmzdbq8(1 zYdLgiv|n4|!*u&n)wt3VJHmg`I4-GYYK^hJ5>}8Xb4p*MN8{JAf2knpvD)EI+TU-y zgiIaeQW5;{rQK}p9Ud{cKw~V5g;0RE@RLJ870!VYk$fj9pskhxp#adWjXs(DWdZH? zk8(w5qM3GoDtP$F`1DUs8uJNGfUs9%qMl?Zmnr0!c7O8@VTK-xjpRkrcQb~O; zXx#J&D^LTMYA`m7_oVZ0EETMV&!l7iS6NOL(ed$K)5I?gy{C~9E5nL}DR9L;`DB8|g~}Gg_J!bt9#x0XnD@3GPGS&n~hABqJCtyp3{xvMo zptl{5s?@jUa)G%HI)YvhGKWeQJ}DiBMza9hpqo?w8u=3N>&5(ji_7PMTr~olVd_ z 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 NeV&r#XD|TZzX1CDMA-lU literal 0 HcmV?d00001 diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.md5 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.md5 new file mode 100644 index 00000000..48f1442a --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.md5 @@ -0,0 +1 @@ +76213ed823c855207079b01f59790feb \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.sha1 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.sha1 new file mode 100644 index 00000000..ab40e539 --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.sha1 @@ -0,0 +1 @@ +ca0d5b62e49ac6c02e67d8ac92e145ed7c2191dc \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.sha256 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.sha256 new file mode 100644 index 00000000..ae9f76ac --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.sha256 @@ -0,0 +1 @@ +7debf7f6f9a7cf6297e658c36d09dbc8e9b3158fadab0cfaa1fc19827e473807 \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.sha512 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.sha512 new file mode 100644 index 00000000..dfffb384 --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.aar.sha512 @@ -0,0 +1 @@ +a8391073c593aec3c8ba4456f86a7bee002062ef5974d873ea6bf1e14d5ac17d772abc5f5539d276f8444b9c46c04cfdcd563be5e9cf00161adf4293e08f0eae \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module new file mode 100644 index 00000000..83af98f2 --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module @@ -0,0 +1,177 @@ +{ + "formatVersion": "1.1", + "component": { + "group": "BareExpo", + "module": "expo.modules.image", + "version": "3.0.11", + "attributes": { + "org.gradle.status": "release" + } + }, + "createdBy": { + "gradle": { + "version": "8.14.3" + } + }, + "variants": [ + { + "name": "releaseVariantReleaseApiPublication", + "attributes": { + "org.gradle.category": "library", + "org.gradle.dependency.bundling": "external", + "org.gradle.libraryelements": "aar", + "org.gradle.usage": "java-api" + }, + "dependencies": [ + { + "group": "com.github.bumptech.glide", + "module": "glide", + "version": { + "requires": "4.16.0" + } + }, + { + "group": "com.caverock", + "module": "androidsvg-aar", + "version": { + "requires": "1.4" + } + }, + { + "group": "com.github.bumptech.glide", + "module": "okhttp3-integration", + "version": { + "requires": "4.11.0" + } + }, + { + "group": "com.squareup.okhttp3", + "module": "okhttp", + "version": { + "requires": "4.9.2" + } + } + ], + "files": [ + { + "name": "expo.modules.image-3.0.11.aar", + "url": "expo.modules.image-3.0.11.aar", + "size": 304446, + "sha512": "a8391073c593aec3c8ba4456f86a7bee002062ef5974d873ea6bf1e14d5ac17d772abc5f5539d276f8444b9c46c04cfdcd563be5e9cf00161adf4293e08f0eae", + "sha256": "7debf7f6f9a7cf6297e658c36d09dbc8e9b3158fadab0cfaa1fc19827e473807", + "sha1": "ca0d5b62e49ac6c02e67d8ac92e145ed7c2191dc", + "md5": "76213ed823c855207079b01f59790feb" + } + ] + }, + { + "name": "releaseVariantReleaseRuntimePublication", + "attributes": { + "org.gradle.category": "library", + "org.gradle.dependency.bundling": "external", + "org.gradle.libraryelements": "aar", + "org.gradle.usage": "java-runtime" + }, + "dependencies": [ + { + "group": "org.jetbrains.kotlin", + "module": "kotlin-stdlib-jdk7", + "version": { + "requires": "2.1.20" + } + }, + { + "group": "com.facebook.react", + "module": "react-android" + }, + { + "group": "com.github.penfeizhou.android.animation", + "module": "glide-plugin", + "version": { + "requires": "3.0.5" + } + }, + { + "group": "com.github.bumptech.glide", + "module": "avif-integration", + "version": { + "requires": "4.16.0" + } + }, + { + "group": "org.jetbrains.kotlinx", + "module": "kotlinx-coroutines-core", + "version": { + "requires": "1.5.1" + } + }, + { + "group": "jp.wasabeef", + "module": "glide-transformations", + "version": { + "requires": "4.3.0" + } + }, + { + "group": "com.github.bumptech.glide", + "module": "glide", + "version": { + "requires": "4.16.0" + } + }, + { + "group": "com.caverock", + "module": "androidsvg-aar", + "version": { + "requires": "1.4" + } + }, + { + "group": "com.github.bumptech.glide", + "module": "okhttp3-integration", + "version": { + "requires": "4.11.0" + } + }, + { + "group": "com.squareup.okhttp3", + "module": "okhttp", + "version": { + "requires": "4.9.2" + } + } + ], + "files": [ + { + "name": "expo.modules.image-3.0.11.aar", + "url": "expo.modules.image-3.0.11.aar", + "size": 304446, + "sha512": "a8391073c593aec3c8ba4456f86a7bee002062ef5974d873ea6bf1e14d5ac17d772abc5f5539d276f8444b9c46c04cfdcd563be5e9cf00161adf4293e08f0eae", + "sha256": "7debf7f6f9a7cf6297e658c36d09dbc8e9b3158fadab0cfaa1fc19827e473807", + "sha1": "ca0d5b62e49ac6c02e67d8ac92e145ed7c2191dc", + "md5": "76213ed823c855207079b01f59790feb" + } + ] + }, + { + "name": "releaseVariantReleaseSourcePublication", + "attributes": { + "org.gradle.category": "documentation", + "org.gradle.dependency.bundling": "external", + "org.gradle.docstype": "sources", + "org.gradle.usage": "java-runtime" + }, + "files": [ + { + "name": "expo.modules.image-3.0.11-sources.jar", + "url": "expo.modules.image-3.0.11-sources.jar", + "size": 67772, + "sha512": "456d35849ab903ceb2f587d3e17c93c0ec101f891c94e966e9b19c388b1482ec15f587016a544f23ab2ba1f04838772b1c49dc2c3ab5000b204dac2fed260571", + "sha256": "a826ab41c2b8020ac3b17ef1025d8acbe10a79e58cbd642dd290cb094c06af37", + "sha1": "182ad66f47b9849c807dcaee9dfc890db2f97b79", + "md5": "06714bdcfeed575b7b421f37d7a51e11" + } + ] + } + ] +} diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.md5 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.md5 new file mode 100644 index 00000000..83ace1af --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.md5 @@ -0,0 +1 @@ +20b05716e8e814a5a05ba82afba4fd78 \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.sha1 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.sha1 new file mode 100644 index 00000000..11af810f --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.sha1 @@ -0,0 +1 @@ +7e998c5f54d4d81da0d9ace549c4d77f5b5ecafc \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.sha256 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.sha256 new file mode 100644 index 00000000..95aebd2d --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.sha256 @@ -0,0 +1 @@ +88830fbda7cf2c89acb7a6a5a7d0ff4bb5423607ff5babcbad0bf0ae76d7753a \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.sha512 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.sha512 new file mode 100644 index 00000000..6522b1aa --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.module.sha512 @@ -0,0 +1 @@ +e575acb053932b77d4f75d050fa7104ca06f16e3a356a54148e44de49490699f1ef80bfe2a57ff242ad6bd38defda000703eba748325ab2689e6be8b5512ceb5 \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom new file mode 100644 index 00000000..1202bc1c --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom @@ -0,0 +1,88 @@ + + + + + + + + 4.0.0 + BareExpo + expo.modules.image + 3.0.11 + aar + expo.modules.image + https://github.com/expo/expo + + + MIT License + https://github.com/expo/expo/blob/main/LICENSE + + + + https://github.com/expo/expo.git + https://github.com/expo/expo.git + https://github.com/expo/expo + + + + com.github.bumptech.glide + glide + 4.16.0 + compile + + + com.caverock + androidsvg-aar + 1.4 + compile + + + com.github.bumptech.glide + okhttp3-integration + 4.11.0 + compile + + + com.squareup.okhttp3 + okhttp + 4.9.2 + compile + + + org.jetbrains.kotlin + kotlin-stdlib-jdk7 + 2.1.20 + runtime + + + com.facebook.react + react-android + runtime + + + com.github.penfeizhou.android.animation + glide-plugin + 3.0.5 + runtime + + + com.github.bumptech.glide + avif-integration + 4.16.0 + runtime + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + 1.5.1 + runtime + + + jp.wasabeef + glide-transformations + 4.3.0 + runtime + + + diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.md5 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.md5 new file mode 100644 index 00000000..e18ba03c --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.md5 @@ -0,0 +1 @@ +c888e14c481941a5e163eb39e1ab817a \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.sha1 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.sha1 new file mode 100644 index 00000000..3f8847da --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.sha1 @@ -0,0 +1 @@ +29d68ce402d3885681b88aec89c4f5b4ee6b3a93 \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.sha256 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.sha256 new file mode 100644 index 00000000..047b559f --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.sha256 @@ -0,0 +1 @@ +8add255d0869c2af89cb0009a3ed826b62f3651ab59d0c6a69bafbf716b55cc8 \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.sha512 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.sha512 new file mode 100644 index 00000000..9d8858d8 --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/3.0.11/expo.modules.image-3.0.11.pom.sha512 @@ -0,0 +1 @@ +d2776670f28b88026cffcae63fd775841030f649e677bae7b22d3510d3dc33563a34c73cf4fff779da90d00eb18197d856963dd9c304712a7caae38ff4574b7f \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml new file mode 100644 index 00000000..caed300b --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml @@ -0,0 +1,13 @@ + + + BareExpo + expo.modules.image + + 3.0.11 + 3.0.11 + + 3.0.11 + + 20251205064609 + + diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.md5 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.md5 new file mode 100644 index 00000000..36b60289 --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.md5 @@ -0,0 +1 @@ +b13794474eea9541e8794e0f6e043021 \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha1 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha1 new file mode 100644 index 00000000..48cffb79 --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha1 @@ -0,0 +1 @@ +3c9f96d2387cbc30052f39a4a6e422d2ea39e45a \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha256 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha256 new file mode 100644 index 00000000..ecee7261 --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha256 @@ -0,0 +1 @@ +4edbc3a6d135ff0b6cf9ef0a54ac9dc1af2bf02127c344734bf7c3cd24d09c25 \ No newline at end of file diff --git a/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha512 b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha512 new file mode 100644 index 00000000..f56c644c --- /dev/null +++ b/node_modules/expo-image/local-maven-repo/BareExpo/expo.modules.image/maven-metadata.xml.sha512 @@ -0,0 +1 @@ +0ea3c3eceb4a4d6bf779c6c581c070414ad22d8975452ad01a151f6ceaa00f34849a2fa0560a54328db8cdcdccf002a463e79343f15f3daba5591a4104396abc \ No newline at end of file diff --git a/node_modules/expo-image/package.json b/node_modules/expo-image/package.json new file mode 100644 index 00000000..c7191f5d --- /dev/null +++ b/node_modules/expo-image/package.json @@ -0,0 +1,49 @@ +{ + "name": "expo-image", + "title": "Expo Image", + "version": "3.0.11", + "description": "A cross-platform, performant image component for React Native and Expo with Web support", + "main": "src/index.ts", + "types": "build/index.d.ts", + "sideEffects": false, + "scripts": { + "build": "expo-module build", + "clean": "expo-module clean", + "lint": "expo-module lint", + "test": "expo-module test", + "test:rsc": "jest --config jest-rsc.config.js", + "prepare": "expo-module prepare", + "prepublishOnly": "expo-module prepublishOnly", + "expo-module": "expo-module" + }, + "homepage": "https://docs.expo.dev/versions/latest/sdk/image/", + "repository": { + "type": "git", + "url": "https://github.com/expo/expo.git", + "directory": "packages/expo-image" + }, + "keywords": [ + "react-native" + ], + "author": "650 Industries, Inc.", + "license": "MIT", + "dependencies": {}, + "devDependencies": { + "expo-module-scripts": "^5.0.8" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*", + "react-native-web": "*" + }, + "peerDependenciesMeta": { + "react-native-web": { + "optional": true + } + }, + "jest": { + "preset": "expo-module-scripts" + }, + "gitHead": "172a69f5f70c1d0e043e1532f924de97210cabc3" +} diff --git a/node_modules/expo-image/src/ExpoImage.tsx b/node_modules/expo-image/src/ExpoImage.tsx new file mode 100644 index 00000000..e9425f2d --- /dev/null +++ b/node_modules/expo-image/src/ExpoImage.tsx @@ -0,0 +1,124 @@ +import { requireNativeViewManager } from 'expo-modules-core'; +import React from 'react'; +import { NativeSyntheticEvent, StyleSheet, Platform, processColor } from 'react-native'; + +import { + ImageErrorEventData, + ImageLoadEventData, + ImageNativeProps, + ImageProgressEventData, +} from './Image.types'; + +const NativeExpoImage = requireNativeViewManager('ExpoImage'); + +function withDeprecatedNativeEvent( + event: NativeSyntheticEvent +): NativeEvent { + Object.defineProperty(event.nativeEvent, 'nativeEvent', { + get() { + console.warn( + '[expo-image]: Accessing event payload through "nativeEvent" is deprecated, it is now part of the event object itself' + ); + return event.nativeEvent; + }, + }); + return event.nativeEvent; +} + +class ExpoImage extends React.PureComponent { + // NOTE(@kitten): native methods + startAnimating!: () => Promise | unknown; + stopAnimating!: () => Promise | unknown; + lockResourceAsync!: () => Promise; + unlockResourceAsync!: () => Promise; + reloadAsync!: () => Promise; + + onLoadStart = () => { + this.props.onLoadStart?.(); + }; + + onLoad = (event: NativeSyntheticEvent) => { + this.props.onLoad?.(withDeprecatedNativeEvent(event)); + this.onLoadEnd(); + }; + + onProgress = (event: NativeSyntheticEvent) => { + this.props.onProgress?.(withDeprecatedNativeEvent(event)); + }; + + onError = (event: NativeSyntheticEvent) => { + this.props.onError?.(withDeprecatedNativeEvent(event)); + this.onLoadEnd(); + }; + + onLoadEnd = () => { + this.props.onLoadEnd?.(); + }; + + render() { + const { style, accessibilityLabel, alt, ...props } = this.props; + const resolvedStyle = StyleSheet.flatten(style); + + // Shadows behave different on iOS, Android & Web. + // Android uses the `elevation` prop, whereas iOS + // and web use the regular `shadow...` props. + if (Platform.OS === 'android') { + delete resolvedStyle.shadowColor; + delete resolvedStyle.shadowOffset; + delete resolvedStyle.shadowOpacity; + delete resolvedStyle.shadowRadius; + } else { + // @ts-expect-error + delete resolvedStyle.elevation; + } + + // @ts-ignore + const backgroundColor = processColor(resolvedStyle.backgroundColor); + // On Android, we have to set the `backgroundColor` directly on the correct component. + // So we have to remove it from styles. Otherwise, the background color won't take into consideration the border-radius. + if (Platform.OS === 'android') { + delete resolvedStyle.backgroundColor; + } + + const tintColor = processColor(props.tintColor || resolvedStyle.tintColor); + + const borderColor = processColor(resolvedStyle.borderColor); + // @ts-ignore + const borderStartColor = processColor(resolvedStyle.borderStartColor); + // @ts-ignore + const borderEndColor = processColor(resolvedStyle.borderEndColor); + // @ts-ignore + const borderLeftColor = processColor(resolvedStyle.borderLeftColor); + // @ts-ignore + const borderRightColor = processColor(resolvedStyle.borderRightColor); + // @ts-ignore + const borderTopColor = processColor(resolvedStyle.borderTopColor); + // @ts-ignore + const borderBottomColor = processColor(resolvedStyle.borderBottomColor); + + return ( + + ); + } +} + +export default ExpoImage; diff --git a/node_modules/expo-image/src/ExpoImage.web.tsx b/node_modules/expo-image/src/ExpoImage.web.tsx new file mode 100644 index 00000000..2a49ff00 --- /dev/null +++ b/node_modules/expo-image/src/ExpoImage.web.tsx @@ -0,0 +1,172 @@ +import React from 'react'; +// TODO(@kitten): We shouldn't be importing all of react-native-web or rely on it for a web module in this way optimally +import { View } from 'react-native-web'; + +import type { ImageNativeProps, ImageSource, ImageLoadEventData, ImageRef } from './Image.types'; +import AnimationManager, { AnimationManagerNode } from './web/AnimationManager'; +import ImageWrapper from './web/ImageWrapper'; +import loadStyle from './web/imageStyles'; +import useSourceSelection from './web/useSourceSelection'; + +loadStyle(); + +function onLoadAdapter(onLoad?: (event: ImageLoadEventData) => void) { + return (event: React.SyntheticEvent) => { + const target = event.target as HTMLImageElement; + onLoad?.({ + source: { + url: target.currentSrc, + width: target.naturalWidth, + height: target.naturalHeight, + mediaType: null, + }, + cacheType: 'none', + }); + }; +} + +function onErrorAdapter(onError?: { (event: { error: string }): void }) { + return ({ source }: { source?: ImageSource | null }) => { + onError?.({ + error: `Failed to load image from url: ${source?.uri}`, + }); + }; +} + +// Used for flip transitions to mimic native animations +function setCssVariablesForFlipTransitions(element: HTMLElement, size: DOMRect) { + element?.style.setProperty('--expo-image-width', `${size.width}px`); + element?.style.setProperty('--expo-image-height', `${size.height}px`); +} + +function isFlipTransition(transition: ImageNativeProps['transition']) { + return ( + transition?.effect === 'flip-from-bottom' || + transition?.effect === 'flip-from-top' || + transition?.effect === 'flip-from-left' || + transition?.effect === 'flip-from-right' + ); +} + +function getAnimationKey( + source: ImageSource | ImageRef | undefined, + recyclingKey?: string | null +): string { + const uri = (source && 'uri' in source && source.uri) || ''; + return recyclingKey ? [recyclingKey, uri].join('-') : uri; +} + +export default function ExpoImage({ + source, + placeholder, + contentFit, + contentPosition, + placeholderContentFit, + cachePolicy, + onLoad, + transition, + onError, + responsivePolicy, + onLoadEnd, + onDisplay, + priority, + blurRadius, + recyclingKey, + style, + nativeViewRef, + accessibilityLabel, + alt, + tintColor, + containerViewRef, + ...props +}: ImageNativeProps) { + const imagePlaceholderContentFit = placeholderContentFit || 'scale-down'; + const imageHashStyle = { + objectFit: placeholderContentFit || contentFit, + }; + const selectedSource = useSourceSelection( + source, + responsivePolicy, + // TODO(@vonovak): this cast is a workaround + containerViewRef as React.RefObject, + isFlipTransition(transition) ? setCssVariablesForFlipTransitions : null + ); + + // TODO(@kitten): This should narrow before accessing `placeholder?.[0]` + const firstPlaceholder = (placeholder as (typeof placeholder & ImageSource[]) | undefined)?.[0]; + const initialNodeAnimationKey = getAnimationKey(firstPlaceholder, recyclingKey); + const initialNode: AnimationManagerNode | null = firstPlaceholder?.uri + ? [ + initialNodeAnimationKey, + ({ onAnimationFinished }) => + (className, style) => ( + | undefined} + source={firstPlaceholder} + style={{ + objectFit: imagePlaceholderContentFit, + ...(blurRadius ? { filter: `blur(${blurRadius}px)` } : {}), + ...style, + }} + className={className} + events={{ + onTransitionEnd: [onAnimationFinished], + }} + contentPosition={{ left: '50%', top: '50%' }} + hashPlaceholderContentPosition={contentPosition} + hashPlaceholderStyle={imageHashStyle} + accessibilityLabel={accessibilityLabel ?? alt} + cachePolicy={cachePolicy} + priority={priority} + tintColor={tintColor} + /> + ), + ] + : null; + + // @ts-expect-error: TODO(@kitten): This was implicitly cast to `any`, but with correct types this is now a mismatch + const currentNodeAnimationKey = getAnimationKey(selectedSource ?? firstPlaceholder, recyclingKey); + const currentNode: AnimationManagerNode = [ + currentNodeAnimationKey, + ({ onAnimationFinished, onReady, onMount, onError: onErrorInner }) => + (className, style) => ( + | undefined} + // @ts-expect-error: TODO(@kitten): This was implicitly cast to `any`, but with correct types this is now a mismatch + source={selectedSource || firstPlaceholder} + events={{ + onError: [onErrorAdapter(onError), onLoadEnd, onErrorInner], + onLoad: [onLoadAdapter(onLoad), onLoadEnd, onReady], + onMount: [onMount], + onTransitionEnd: [onAnimationFinished], + onDisplay: [onDisplay], + }} + style={{ + objectFit: selectedSource ? contentFit : imagePlaceholderContentFit, + ...(blurRadius ? { filter: `blur(${blurRadius}px)` } : {}), + ...style, + }} + className={className} + cachePolicy={cachePolicy} + priority={priority} + contentPosition={selectedSource ? contentPosition : { top: '50%', left: '50%' }} + hashPlaceholderContentPosition={contentPosition} + hashPlaceholderStyle={imageHashStyle} + accessibilityLabel={accessibilityLabel} + tintColor={tintColor} + /> + ), + ]; + return ( + + + {currentNode} + + + ); +} diff --git a/node_modules/expo-image/src/Image.tsx b/node_modules/expo-image/src/Image.tsx new file mode 100644 index 00000000..b0489c1d --- /dev/null +++ b/node_modules/expo-image/src/Image.tsx @@ -0,0 +1,262 @@ +'use client'; + +import { Platform, createSnapshotFriendlyRef } from 'expo-modules-core'; +import React from 'react'; +import { StyleSheet, type View } from 'react-native'; + +import ExpoImage from './ExpoImage'; +import { + ImageLoadOptions, + ImagePrefetchOptions, + ImageProps, + ImageRef, + ImageSource, +} from './Image.types'; +import ImageModule from './ImageModule'; +import { resolveContentFit, resolveContentPosition, resolveTransition } from './utils'; +import { resolveSource, resolveSources } from './utils/resolveSources'; + +let loggedDefaultSourceDeprecationWarning = false; +let loggedRenderingChildrenWarning = false; + +export class Image extends React.PureComponent { + nativeViewRef: React.RefObject; + containerViewRef: React.RefObject; + + constructor(props: ImageProps) { + super(props); + this.nativeViewRef = createSnapshotFriendlyRef(); + this.containerViewRef = createSnapshotFriendlyRef(); + } + + // Reanimated support on web + getAnimatableRef = () => { + if (Platform.OS === 'web') { + return this.containerViewRef.current; + } else { + return this; + } + }; + + /** + * @hidden + */ + static Image = ImageModule.Image; + + /** + * Preloads images at the given URLs that can be later used in the image view. + * Preloaded images are cached to the memory and disk by default, so make sure + * to use `disk` (default) or `memory-disk` [cache policy](#cachepolicy). + * @param urls - A URL string or an array of URLs of images to prefetch. + * @param {ImagePrefetchOptions['cachePolicy']} cachePolicy - The cache policy for prefetched images. + * @return A promise resolving to `true` as soon as all images have been + * successfully prefetched. If an image fails to be prefetched, the promise + * will immediately resolve to `false` regardless of whether other images have + * finished prefetching. + */ + static async prefetch( + urls: string | string[], + cachePolicy?: ImagePrefetchOptions['cachePolicy'] + ): Promise; + /** + * Preloads images at the given URLs that can be later used in the image view. + * Preloaded images are cached to the memory and disk by default, so make sure + * to use `disk` (default) or `memory-disk` [cache policy](#cachepolicy). + * @param urls - A URL string or an array of URLs of images to prefetch. + * @param options - Options for prefetching images. + * @return A promise resolving to `true` as soon as all images have been + * successfully prefetched. If an image fails to be prefetched, the promise + * will immediately resolve to `false` regardless of whether other images have + * finished prefetching. + */ + static async prefetch(urls: string | string[], options?: ImagePrefetchOptions): Promise; + static async prefetch( + urls: string | string[], + options?: ImagePrefetchOptions['cachePolicy'] | ImagePrefetchOptions + ): Promise { + let cachePolicy: ImagePrefetchOptions['cachePolicy'] = 'memory-disk'; + let headers: ImagePrefetchOptions['headers']; + switch (typeof options) { + case 'string': + cachePolicy = options; + break; + case 'object': + cachePolicy = options.cachePolicy ?? cachePolicy; + headers = options.headers; + break; + } + + return ImageModule.prefetch(Array.isArray(urls) ? urls : [urls], cachePolicy, headers); + } + + /** + * Asynchronously clears all images stored in memory. + * @platform android + * @platform ios + * @return A promise resolving to `true` when the operation succeeds. + * It may resolve to `false` on Android when the activity is no longer available. + * Resolves to `false` on Web. + */ + static async clearMemoryCache(): Promise { + return await ImageModule.clearMemoryCache(); + } + + /** + * Asynchronously clears all images from the disk cache. + * @platform android + * @platform ios + * @return A promise resolving to `true` when the operation succeeds. + * It may resolve to `false` on Android when the activity is no longer available. + * Resolves to `false` on Web. + */ + static async clearDiskCache(): Promise { + return await ImageModule.clearDiskCache(); + } + + /** + * Asynchronously checks if an image exists in the disk cache and resolves to + * the path of the cached image if it does. + * @param cacheKey - The cache key for the requested image. Unless you have set + * a custom cache key, this will be the source URL of the image. + * @platform android + * @platform ios + * @return A promise resolving to the path of the cached image. It will resolve + * to `null` if the image does not exist in the cache. + */ + static async getCachePathAsync(cacheKey: string): Promise { + return await ImageModule.getCachePathAsync(cacheKey); + } + + /** + * Asynchronously generates a [Blurhash](https://blurha.sh) from an image. + * @param source - The image source, either a URL (string) or an ImageRef + * @param numberOfComponents - The number of components to encode the blurhash with. + * Must be between 1 and 9. Defaults to `[4, 3]`. + * @platform android + * @platform ios + * @return A promise resolving to the blurhash string. + */ + static async generateBlurhashAsync( + source: string | ImageRef, + numberOfComponents: [number, number] | { width: number; height: number } + ): Promise { + return ImageModule.generateBlurhashAsync(source, numberOfComponents); + } + + /** + * Asynchronously generates a [Thumbhash](https://evanw.github.io/thumbhash/) from an image. + * @param source - The image source, either a URL (string) or an ImageRef + * @platform android + * @platform ios + * @return A promise resolving to the thumbhash string. + */ + static async generateThumbhashAsync(source: string | ImageRef): Promise { + return ImageModule.generateThumbhashAsync(source); + } + + /** + * Asynchronously starts playback of the view's image if it is animated. + * @platform android + * @platform ios + */ + async startAnimating(): Promise { + await this.nativeViewRef.current?.startAnimating(); + } + + /** + * Asynchronously stops the playback of the view's image if it is animated. + * @platform android + * @platform ios + */ + async stopAnimating(): Promise { + await this.nativeViewRef.current?.stopAnimating(); + } + + /** + * Prevents the resource from being reloaded by locking it. + * @platform android + * @platform ios + */ + async lockResourceAsync(): Promise { + await this.nativeViewRef.current?.lockResourceAsync(); + } + + /** + * Releases the lock on the resource, allowing it to be reloaded. + * @platform android + * @platform ios + */ + async unlockResourceAsync(): Promise { + await this.nativeViewRef.current?.unlockResourceAsync(); + } + + /** + * Reloads the resource, ignoring lock. + * @platform android + * @platform ios + */ + async reloadAsync(): Promise { + await this.nativeViewRef.current?.reloadAsync(); + } + + /** + * Loads an image from the given source to memory and resolves to + * an object that references the native image instance. + * @platform android + * @platform ios + * @platform web + */ + static async loadAsync( + source: ImageSource | string | number, + options?: ImageLoadOptions + ): Promise { + const resolvedSource = resolveSource(source) as ImageSource; + return await ImageModule.loadAsync(resolvedSource, options); + } + + render() { + const { + style, + source, + placeholder, + contentFit, + contentPosition, + transition, + fadeDuration, + resizeMode: resizeModeProp, + defaultSource, + loadingIndicatorSource, + ...restProps + } = this.props; + + const { resizeMode: resizeModeStyle, ...restStyle } = StyleSheet.flatten(style) || {}; + const resizeMode = resizeModeProp ?? resizeModeStyle; + + if ((defaultSource || loadingIndicatorSource) && !loggedDefaultSourceDeprecationWarning) { + console.warn( + '[expo-image]: `defaultSource` and `loadingIndicatorSource` props are deprecated, use `placeholder` instead' + ); + loggedDefaultSourceDeprecationWarning = true; + } + // @ts-expect-error + if (restProps.children && !loggedRenderingChildrenWarning) { + console.warn( + 'The component does not support children. If you want to render content on top of the image, consider using the component or absolute positioning.' + ); + loggedRenderingChildrenWarning = true; + } + return ( + + ); + } +} diff --git a/node_modules/expo-image/src/Image.types.ts b/node_modules/expo-image/src/Image.types.ts new file mode 100644 index 00000000..9dec0e7a --- /dev/null +++ b/node_modules/expo-image/src/Image.types.ts @@ -0,0 +1,613 @@ +import type { NativeModule, SharedRef, SharedRefType } from 'expo'; +import { ImageStyle as RNImageStyle, StyleProp, View, ViewProps, ViewStyle } from 'react-native'; + +import ExpoImage from './ExpoImage'; + +export type ImageSource = { + /** + * A string representing the resource identifier for the image, + * which could be an HTTPS address, a local file path, or the name of a static image resource. + */ + uri?: string; + /** + * An object representing the HTTP headers to send along with the request for a remote image. + * On web requires the `Access-Control-Allow-Origin` header returned by the server to include the current domain. + */ + headers?: Record; + /** + * Can be specified if known at build time, in which case the value + * will be used to set the default `` component dimension. + */ + width?: number | null; + /** + * Can be specified if known at build time, in which case the value + * will be used to set the default `` component dimension. + */ + height?: number | null; + + /** + * A string used to generate the image [`placeholder`](#placeholder). For example, + * `placeholder={blurhash}`. If `uri` is provided as the value of the `source` prop, + * this is ignored since the `source` can only have `blurhash` or `uri`. + * + * When using the blurhash, you should also provide `width` and `height` (higher values reduce performance), + * otherwise their default value is `16`. + * For more information, see [`woltapp/blurhash`](https://github.com/woltapp/blurhash) repository. + */ + blurhash?: string; + + /** + * A string used to generate the image [`placeholder`](#placeholder). For example, + * `placeholder={thumbhash}`. If `uri` is provided as the value of the `source` prop, + * this is ignored since the `source` can only have `thumbhash` or `uri`. + * + * For more information, see [`thumbhash website`](https://evanw.github.io/thumbhash/). + */ + thumbhash?: string; + + /** + * The cache key used to query and store this specific image. + * If not provided, the `uri` is used also as the cache key. + */ + cacheKey?: string; + /** + * The max width of the viewport for which this source should be selected. + * Has no effect if `source` prop is not an array or has only 1 element. + * Has no effect if `responsivePolicy` is not set to `static`. + * Ignored if `blurhash` or `thumbhash` is provided (image hashes are never selected if passed in an array). + * @platform web + */ + webMaxViewportWidth?: number; + /** + * Whether the image is animated (an animated GIF or WebP for example). + * @platform android + * @platform ios + */ + isAnimated?: boolean; +}; + +/** + * @hidden + */ +export type ImageStyle = RNImageStyle; + +/** + * Determines how the image should be resized to fit its container. + * @hidden Described in the {@link ImageProps['contentFit']} + */ +export type ImageContentFit = 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'; + +/** + * Determines which format should be used to decode the image. + * It's suggestion for the platform to use the specified format, but it's not guaranteed. + * @hidden Described in the {@link ImageProps['decodeFormat']} + */ +export type ImageDecodeFormat = 'argb' | 'rgb'; + +/** + * Some props are from React Native Image that Expo Image supports (more or less) for easier migration, + * but all of them are deprecated and might be removed in the future. + */ +export interface ImageProps extends Omit { + /** @hidden */ + style?: StyleProp; + + /** + * The image source, either a remote URL, a local file resource or a number that is the result of the `require()` function. + * When provided as an array of sources, the source that fits best into the container size and is closest to the screen scale + * will be chosen. In this case it is important to provide `width`, `height` and `scale` properties. + */ + source?: ImageSource | string | number | ImageSource[] | string[] | SharedRefType<'image'> | null; + + /** + * An image to display while loading the proper image and no image has been displayed yet or the source is unset. + * + * > **Note**: The default value for placeholder's content fit is 'scale-down', which differs from the source image's default value. + * > Using a lower-resolution placeholder may cause flickering due to scaling differences between it and the final image. + * > To prevent this, you can set the [`placeholderContentFit`](#placeholdercontentfit) to match the [`contentFit`](#contentfit) value. + */ + placeholder?: + | ImageSource + | string + | number + | ImageSource[] + | string[] + | SharedRefType<'image'> + | null; + + /** + * Determines how the image should be resized to fit its container. This property tells the image to fill the container + * in a variety of ways; such as "preserve that aspect ratio" or "stretch up and take up as much space as possible". + * It mirrors the CSS [`object-fit`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) property. + * + * - `'cover'` - The image is sized to maintain its aspect ratio while filling the container box. + * If the image's aspect ratio does not match the aspect ratio of its box, then the object will be clipped to fit. + * + * - `'contain'` - The image is scaled down or up to maintain its aspect ratio while fitting within the container box. + * + * - `'fill'` - The image is sized to entirely fill the container box. If necessary, the image will be stretched or squished to fit. + * + * - `'none'` - The image is not resized and is centered by default. + * When specified, the exact position can be controlled with [`contentPosition`](#contentposition) prop. + * + * - `'scale-down'` - The image is sized as if `none` or `contain` were specified, whichever would result in a smaller concrete image size. + * + * @default 'cover' + */ + contentFit?: ImageContentFit; + + /** + * Determines how the placeholder should be resized to fit its container. Available resize modes are the same as for the [`contentFit`](#contentfit) prop. + * @default 'scale-down' + */ + placeholderContentFit?: ImageContentFit; + + /** + * It is used together with [`contentFit`](#contentfit) to specify how the image should be positioned with x/y coordinates inside its own container. + * An equivalent of the CSS [`object-position`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) property. + * @default 'center' + */ + contentPosition?: ImageContentPosition; + + /** + * Describes how the image view should transition the contents when switching the image source.\ + * If provided as a number, it is the duration in milliseconds of the `'cross-dissolve'` effect. + */ + transition?: ImageTransition | number | null; + + /** + * The radius of the blur in points, `0` means no blur effect. + * This effect is not applied to placeholders. + * @default 0 + */ + blurRadius?: number; + + /** + * A color used to tint template images (a bitmap image where only the opacity matters). + * The color is applied to every non-transparent pixel, causing the image's shape to adopt that color. + * This effect is not applied to placeholders. + * @default null + */ + tintColor?: string | null; + + /** + * Priorities for completing loads. If more than one load is queued at a time, + * the load with the higher priority will be started first. + * Priorities are considered best effort, there are no guarantees about the order in which loads will start or finish. + * @default 'normal' + */ + priority?: 'low' | 'normal' | 'high' | null; + + /** + * Determines whether to cache the image and where: on the disk, in the memory or both. + * + * - `'none'` - Image is not cached at all. + * + * - `'disk'` - Image is queried from the disk cache if exists, otherwise it's downloaded and then stored on the disk. + * + * - `'memory'` - Image is cached in memory. Might be useful when you render a high-resolution picture many times. + * Memory cache may be purged very quickly to prevent high memory usage and the risk of out of memory exceptions. + * + * - `'memory-disk'` - Image is cached in memory, but with a fallback to the disk cache. + * + * @default 'disk' + */ + cachePolicy?: 'none' | 'disk' | 'memory' | 'memory-disk' | /** @hidden */ null; + + /** + * Controls the selection of the image source based on the container or viewport size on the web. + * + * If set to `'static'`, the browser selects the correct source based on user's viewport width. Works with static rendering. + * Make sure to set the `'webMaxViewportWidth'` property on each source for best results. + * For example, if an image occupies 1/3 of the screen width, set the `'webMaxViewportWidth'` to 3x the image width. + * The source with the largest `'webMaxViewportWidth'` is used even for larger viewports. + * + * If set to `'initial'`, the component will select the correct source during mount based on container size. Does not work with static rendering. + * + * If set to `'live'`, the component will select the correct source on every resize based on container size. Does not work with static rendering. + * + * @default 'static' + * @platform web + */ + responsivePolicy?: 'live' | 'initial' | 'static'; + + /** + * Changing this prop resets the image view content to blank or a placeholder before loading and rendering the final image. + * This is especially useful for any kinds of recycling views like [FlashList](https://github.com/shopify/flash-list) + * to prevent showing the previous source before the new one fully loads. + * @default null + * @platform android + * @platform ios + */ + recyclingKey?: string | null; + + /** + * Determines if an image should automatically begin playing if it is an + * animated image. + * @default true + * @platform android + * @platform ios + */ + autoplay?: boolean; + + /** + * Called when the image starts to load. + */ + onLoadStart?: () => void; + + /** + * Called when the image load completes successfully. + */ + onLoad?: (event: ImageLoadEventData) => void; + + /** + * Called when the image is loading. Can be called multiple times before the image has finished loading. + * The event object provides details on how many bytes were loaded so far and what's the expected total size. + */ + onProgress?: (event: ImageProgressEventData) => void; + + /** + * Called on an image fetching error. + */ + onError?: (event: ImageErrorEventData) => void; + + /** + * Called when the image load either succeeds or fails. + */ + onLoadEnd?: () => void; + + /** + * Called when the image view successfully rendered the source image. + */ + onDisplay?: () => void; + + // DEPRECATED + + /** + * @deprecated Provides compatibility for [`defaultSource` from React Native Image](https://reactnative.dev/docs/image#defaultsource). + * Use [`placeholder`](#placeholder) prop instead. + */ + defaultSource?: ImageSource | null; + + /** + * @deprecated Provides compatibility for [`loadingIndicatorSource` from React Native Image](https://reactnative.dev/docs/image#loadingindicatorsource). + * Use [`placeholder`](#placeholder) prop instead. + */ + loadingIndicatorSource?: ImageSource | null; + + /** + * @deprecated Provides compatibility for [`resizeMode` from React Native Image](https://reactnative.dev/docs/image#resizemode). + * Note that `"repeat"` option is not supported at all. + * Use the more powerful [`contentFit`](#contentfit) and [`contentPosition`](#contentposition) props instead. + */ + resizeMode?: 'cover' | 'contain' | 'stretch' | 'repeat' | 'center'; + + /** + * @deprecated Provides compatibility for [`fadeDuration` from React Native Image](https://reactnative.dev/docs/image#fadeduration-android). + * Instead use [`transition`](#transition) with the provided duration. + */ + fadeDuration?: number; + + /** + * Whether this View should be focusable with a non-touch input device and receive focus with a hardware keyboard. + * @default false + * @platform android + */ + focusable?: boolean; + + /** + * When true, indicates that the view is an accessibility element. + * When a view is an accessibility element, it groups its children into a single selectable component. + * + * On Android, the `accessible` property will be translated into the native `isScreenReaderFocusable`, + * so it's only affecting the screen readers behaviour. + * @default false + * @platform android + * @platform ios + */ + accessible?: boolean; + + /** + * The text that's read by the screen reader when the user interacts with the image. Sets the `alt` tag on web which is used for web crawlers and link traversal. + * @default undefined + */ + accessibilityLabel?: string; + + /** + * The text that's read by the screen reader when the user interacts with the image. Sets the `alt` tag on web which is used for web crawlers and link traversal. Is an alias for `accessibilityLabel`. + * + * @alias accessibilityLabel + * @default undefined + */ + alt?: string; + + /** + * Enables Live Text interaction with the image. Check official [Apple documentation](https://developer.apple.com/documentation/visionkit/enabling_live_text_interactions_with_images) for more details. + * @default false + * @platform ios 16.0+ + */ + enableLiveTextInteraction?: boolean; + + /** + * Whether the image should be downscaled to match the size of the view container. + * Turning off this functionality could negatively impact the application's performance, particularly when working with large assets. + * However, it would result in smoother image resizing, and end-users would always have access to the highest possible asset quality. + * + * Downscaling is never used when the `contentFit` prop is set to `none` or `fill`. + * @default true + */ + allowDownscaling?: boolean; + + /** + * The format in which the image data should be decoded. + * It's not guaranteed that the platform will use the specified format. + * + * - `'argb'` - The image is decoded into a 32-bit color space with alpha channel (https://developer.android.com/reference/android/graphics/Bitmap.Config#ARGB_8888). + * + * - `'rgb'` - The image is decoded into a 16-bit color space without alpha channel (https://developer.android.com/reference/android/graphics/Bitmap.Config#RGB_565). + * + * @default 'argb' + * @platform android + */ + decodeFormat?: ImageDecodeFormat; + + /** + * Whether to use the Apple's default WebP codec. + * + * Set this prop to `false` to use the official standard-compliant [libwebp](https://github.com/webmproject/libwebp) codec for WebP images. + * The default implementation from Apple is faster and uses less memory but may render animated images with incorrect blending or play them at the wrong framerate. + * @see https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#awebp-coder + * + * @default true + * @platform ios + */ + useAppleWebpCodec?: boolean; + + /** + * Force early resizing of the image to match the container size. + * This option helps to reduce the memory usage of the image view, especially when the image is larger than the container. + * It may affect the `resizeType` and `contentPosition` properties when the image view is resized dynamically. + * + * @default false + * @platform ios + */ + enforceEarlyResizing?: boolean; +} + +/** + * It narrows down some props to types expected by the native/web side. + * @hidden + */ +export interface ImageNativeProps extends ImageProps { + style?: RNImageStyle; + source?: ImageSource[] | SharedRefType<'image'>; + placeholder?: ImageSource[] | SharedRefType<'image'>; + contentPosition?: ImageContentPositionObject; + transition?: ImageTransition | null; + autoplay?: boolean; + nativeViewRef?: React.RefObject; + containerViewRef?: React.RefObject; +} + +/** + * A value that represents the relative position of a single axis. + * + * If `number`, it is a distance in points (logical pixels) from the respective edge.\ + * If `string`, it must be a percentage value where `'100%'` is the difference in size between the container and the image along the respective axis, + * or `'center'` which is an alias for `'50%'` that is the default value. You can read more regarding percentages on the MDN docs for + * [`background-position`](https://developer.mozilla.org/en-US/docs/Web/CSS/background-position#regarding_percentages) that describes this concept well. + */ +export type ImageContentPositionValue = number | string | `${number}%` | `${number}` | 'center'; + +/** + * Specifies the position of the image inside its container. One value controls the x-axis and the second value controls the y-axis. + * + * Additionally, it supports stringified shorthand form that specifies the edges to which to align the image content:\ + * `'center'`, `'top'`, `'right'`, `'bottom'`, `'left'`, `'top center'`, `'top right'`, `'top left'`, `'right center'`, `'right top'`, + * `'right bottom'`, `'bottom center'`, `'bottom right'`, `'bottom left'`, `'left center'`, `'left top'`, `'left bottom'`.\ + * If only one keyword is provided, then the other dimension is set to `'center'` (`'50%'`), so the image is placed in the middle of the specified edge.\ + * As an example, `'top right'` is the same as `{ top: 0, right: 0 }` and `'bottom'` is the same as `{ bottom: 0, left: '50%' }`. + */ +export type ImageContentPosition = + /** + * An object that positions the image relatively to the top-right corner. + */ + | { + top?: ImageContentPositionValue; + right?: ImageContentPositionValue; + } + /** + * An object that positions the image relatively to the top-left corner. + */ + | { + top?: ImageContentPositionValue; + left?: ImageContentPositionValue; + } + /** + * An object that positions the image relatively to the bottom-right corner. + */ + | { + bottom?: ImageContentPositionValue; + right?: ImageContentPositionValue; + } + /** + * An object that positions the image relatively to the bottom-left corner. + */ + | { + bottom?: ImageContentPositionValue; + left?: ImageContentPositionValue; + } + | ImageContentPositionString; + +/** + * It allows you to use an image as a background while rendering other content on top of it. + * It extends all `Image` props but provides separate styling controls for the container and the background image itself. + */ +export interface ImageBackgroundProps extends Omit { + /** The style of the image container. */ + style?: StyleProp | undefined; + /** Style object for the image. */ + imageStyle?: StyleProp | undefined; + /** @hidden */ + children?: React.ReactNode | undefined; +} + +/** + * @hidden It's described as part of {@link ImageContentPosition}. + */ +export type ImageContentPositionString = + | 'center' + | 'top' + | 'right' + | 'bottom' + | 'left' + | 'top center' + | 'top right' + | 'top left' + | 'right center' + | 'right top' + | 'right bottom' + | 'bottom center' + | 'bottom right' + | 'bottom left' + | 'left center' + | 'left top' + | 'left bottom'; + +type OnlyObject = T extends object ? T : never; + +/** + * @hidden It's a conditional type that matches only objects of {@link ImageContentPosition}. + */ +export type ImageContentPositionObject = OnlyObject; + +/** + * An object that describes the smooth transition when switching the image source. + */ +export type ImageTransition = { + /** + * The duration of the transition in milliseconds. + * @default 0 + */ + duration?: number; + + /** + * Specifies the speed curve of the transition effect and how intermediate values are calculated. + * @default 'ease-in-out' + */ + timing?: 'ease-in-out' | 'ease-in' | 'ease-out' | 'linear'; + + /** + * An animation effect used for transition. + * @default 'cross-dissolve' + * + * On Android, only `'cross-dissolve'` is supported. + * On Web, `'curl-up'` and `'curl-down'` effects are not supported. + */ + effect?: + | 'cross-dissolve' + | 'flip-from-top' + | 'flip-from-right' + | 'flip-from-bottom' + | 'flip-from-left' + | 'curl-up' + | 'curl-down' + | null; +}; + +export type ImageLoadEventData = { + cacheType: 'none' | 'disk' | 'memory'; + source: { + url: string; + width: number; + height: number; + mediaType: string | null; + isAnimated?: boolean; + }; +}; + +export type ImageProgressEventData = { + loaded: number; + total: number; +}; + +export type ImageErrorEventData = { + error: string; +}; + +export type ImagePrefetchOptions = { + /** + * The cache policy for prefetched images. + * @default 'memory-disk' + */ + cachePolicy?: 'disk' | 'memory-disk' | 'memory'; + + /** + * A map of headers to use when prefetching the images. + */ + headers?: Record; +}; + +/** + * An object that is a reference to a native image instance โ€“ [Drawable](https://developer.android.com/reference/android/graphics/drawable/Drawable) + * on Android and [UIImage](https://developer.apple.com/documentation/uikit/uiimage) on iOS. + * Instances of this class can be passed as a source to the [Image](#image) component in which case the image is rendered immediately + * since its native representation is already available in the memory. + */ +export declare class ImageRef extends SharedRef<'image'> { + /** + * Logical width of the image. Multiply it by the value in the `scale` property to get the width in pixels. + */ + readonly width: number; + /** + * Logical height of the image. Multiply it by the value in the `scale` property to get the height in pixels. + */ + readonly height: number; + /** + * On iOS, if you load an image from a file whose name includes the `@2x` modifier, the scale is set to **2.0**. All other images are assumed to have a scale factor of **1.0**. + * On Android, it calculates the scale based on the bitmap density divided by screen density. + * + * On all platforms, if you multiply the logical size of the image by this value, you get the dimensions of the image in pixels. + */ + readonly scale: number; + /** + * Media type (also known as MIME type) of the image, based on its format. + * Returns `null` when the format is unknown or not supported. + * @platform ios + */ + readonly mediaType: string | null; + /** + * Whether the referenced image is an animated image. + */ + readonly isAnimated?: boolean; +} + +/** + * @hidden + */ +export declare class ImageNativeModule extends NativeModule { + // TODO: Add missing function declarations + Image: typeof ImageRef; + + loadAsync(source: ImageSource, options?: ImageLoadOptions): Promise; +} + +/** + * An object with options for the [`useImage`](#useimage) hook. + */ +export type ImageLoadOptions = { + /** + * If provided, the image will be automatically resized to not exceed this width in pixels, preserving its aspect ratio. + */ + maxWidth?: number; + + /** + * If provided, the image will be automatically resized to not exceed this height in pixels, preserving its aspect ratio. + */ + maxHeight?: number; + + /** + * Function to call when the image has failed to load. In addition to the error, it also provides a function that retries loading the image. + */ + onError?(error: Error, retry: () => void): void; +}; diff --git a/node_modules/expo-image/src/ImageBackground.tsx b/node_modules/expo-image/src/ImageBackground.tsx new file mode 100644 index 00000000..c2efdc47 --- /dev/null +++ b/node_modules/expo-image/src/ImageBackground.tsx @@ -0,0 +1,16 @@ +'use client'; + +import React from 'react'; +import { View, StyleSheet } from 'react-native'; + +import { Image } from './Image'; +import { ImageBackgroundProps } from './Image.types'; + +export function ImageBackground({ style, imageStyle, children, ...props }: ImageBackgroundProps) { + return ( + + + {children} + + ); +} diff --git a/node_modules/expo-image/src/ImageModule.ts b/node_modules/expo-image/src/ImageModule.ts new file mode 100644 index 00000000..2f541a07 --- /dev/null +++ b/node_modules/expo-image/src/ImageModule.ts @@ -0,0 +1,5 @@ +import { requireNativeModule } from 'expo'; + +import type { ImageNativeModule } from './Image.types'; + +export default requireNativeModule('ExpoImage'); diff --git a/node_modules/expo-image/src/ImageModule.web.ts b/node_modules/expo-image/src/ImageModule.web.ts new file mode 100644 index 00000000..02268f1e --- /dev/null +++ b/node_modules/expo-image/src/ImageModule.web.ts @@ -0,0 +1,76 @@ +import { NativeModule, registerWebModule } from 'expo-modules-core'; + +import type { ImageNativeModule, ImageRef, ImageSource } from './Image.types'; +import ImageRefWeb from './web/ImageRef'; + +class ImageModule extends NativeModule implements ImageNativeModule { + Image: typeof ImageRef = ImageRefWeb; + + async prefetch(urls: string | string[], _: unknown, __: unknown): Promise { + const urlsArray = Array.isArray(urls) ? urls : [urls]; + + return new Promise((resolve) => { + let imagesLoaded = 0; + + urlsArray.forEach((url) => { + const img = new Image(); + img.src = url; + img.onload = () => { + imagesLoaded++; + + if (imagesLoaded === urlsArray.length) { + resolve(true); + } + }; + img.onerror = () => resolve(false); + }); + }); + } + + async clearMemoryCache(): Promise { + return false; + } + + async clearDiskCache(): Promise { + return false; + } + + async loadAsync(source: ImageSource): Promise { + if (!source.uri) { + // TODO: Add support for sources without the uri, e.g. blurhash and thumbhash. + throw new Error('The image source must have the "uri" property defined'); + } + const response = await fetch(source.uri, { + headers: source.headers, + }); + + if (!response.ok) { + throw new Error(`Image request failed with the status code: ${response.status}`); + } + const blob = await response.blob(); + const imageObjectUrl = URL.createObjectURL(blob); + const image = await loadImageElementAsync(imageObjectUrl); + + return ImageRefWeb.init( + imageObjectUrl, + image.width, + image.height, + response.headers.get('Content-Type') + ); + } +} + +/** + * Helper that resolves to an `` element once it finishes loading the given source. + */ +async function loadImageElementAsync(src: string): Promise { + return new Promise((resolve, reject) => { + const image = document.createElement('img'); + + image.onload = () => resolve(image); + image.onerror = () => reject(new Error(`Unable to load the image from '${src}'`)); + image.src = src; + }); +} + +export default registerWebModule(ImageModule, 'ExpoImage'); diff --git a/node_modules/expo-image/src/index.ts b/node_modules/expo-image/src/index.ts new file mode 100644 index 00000000..6fa00663 --- /dev/null +++ b/node_modules/expo-image/src/index.ts @@ -0,0 +1,4 @@ +export * from './Image.types'; +export { Image } from './Image'; +export { ImageBackground } from './ImageBackground'; +export { useImage } from './useImage'; diff --git a/node_modules/expo-image/src/ts-declarations/react-native-assets.d.ts b/node_modules/expo-image/src/ts-declarations/react-native-assets.d.ts new file mode 100644 index 00000000..ed291c83 --- /dev/null +++ b/node_modules/expo-image/src/ts-declarations/react-native-assets.d.ts @@ -0,0 +1 @@ +/// diff --git a/node_modules/expo-image/src/ts-declarations/react-native-web.d.ts b/node_modules/expo-image/src/ts-declarations/react-native-web.d.ts new file mode 100644 index 00000000..81b6686e --- /dev/null +++ b/node_modules/expo-image/src/ts-declarations/react-native-web.d.ts @@ -0,0 +1,3 @@ +declare module 'react-native-web' { + export { View } from 'react-native'; +} diff --git a/node_modules/expo-image/src/useImage.ts b/node_modules/expo-image/src/useImage.ts new file mode 100644 index 00000000..f622e10f --- /dev/null +++ b/node_modules/expo-image/src/useImage.ts @@ -0,0 +1,92 @@ +'use client'; + +import { DependencyList, useEffect, useRef, useState } from 'react'; + +import { Image } from './Image'; +import type { ImageLoadOptions, ImageRef, ImageSource } from './Image.types'; +import { resolveSource } from './utils/resolveSources'; + +/** + * A hook that loads an image from the given source and returns a reference + * to the native image instance, or `null` until the first image is successfully loaded. + * + * It loads a new image every time the `uri` of the provided source changes. + * To trigger reloads in some other scenarios, you can provide an additional dependency list. + * @platform android + * @platform ios + * @platform web + * + * @example + * ```ts + * import { useImage, Image } from 'expo-image'; + * import { Text } from 'react-native'; + * + * export default function MyImage() { + * const image = useImage('https://picsum.photos/1000/800', { + * maxWidth: 800, + * onError(error, retry) { + * console.error('Loading failed:', error.message); + * } + * }); + * + * if (!image) { + * return Image is loading...; + * } + * + * return ; + * } + * ``` + */ +export function useImage( + source: ImageSource | string | number, + options: ImageLoadOptions = {}, + dependencies: DependencyList = [] +): ImageRef | null { + const resolvedSource = resolveSource(source) as ImageSource; + const [image, setImage] = useState(null); + + // Since options are not dependencies of the below effect, we store them in a ref. + // Once the image is asynchronously loaded, the effect will use the most recent options, + // instead of the captured ones (especially important for callbacks that may change in subsequent renders). + const optionsRef = useRef(options); + optionsRef.current = options; + + useEffect(() => { + // We're doing some asynchronous action in this effect, so we should keep track + // if the effect was already cleaned up. In that case, the async action shouldn't change the state. + let isEffectValid = true; + + function loadImage() { + Image.loadAsync(resolvedSource, options) + .then((image) => { + if (isEffectValid) { + setImage(image); + } + }) + .catch((error) => { + if (!isEffectValid) { + return; + } + if (optionsRef.current.onError) { + optionsRef.current.onError(error, loadImage); + } else { + // Print unhandled errors to the console. + console.error( + `Loading an image from '${resolvedSource.uri}' failed, use 'onError' option to handle errors and suppress this message` + ); + console.error(error); + } + }); + } + + loadImage(); + + return () => { + // Invalidate the effect and release the shared object to free up memory. + isEffectValid = false; + image?.release(); + }; + }, [resolvedSource.uri, ...dependencies]); + + return image; +} diff --git a/node_modules/expo-image/src/utils.ts b/node_modules/expo-image/src/utils.ts new file mode 100644 index 00000000..64c2fd61 --- /dev/null +++ b/node_modules/expo-image/src/utils.ts @@ -0,0 +1,126 @@ +import { SharedRef } from 'expo'; +import type { SharedRefType } from 'expo'; +import { type ImageResizeMode } from 'react-native'; + +import { + ImageContentFit, + ImageContentPosition, + ImageContentPositionObject, + ImageContentPositionString, + ImageProps, + ImageTransition, +} from './Image.types'; + +let loggedResizeModeDeprecationWarning = false; +let loggedRepeatDeprecationWarning = false; +let loggedFadeDurationDeprecationWarning = false; + +/** + * If the `contentFit` is not provided, it's resolved from the equivalent `resizeMode` prop + * that we support to provide compatibility with React Native Image. + */ +export function resolveContentFit( + contentFit?: ImageContentFit, + resizeMode?: ImageResizeMode +): ImageContentFit { + if (contentFit) { + return contentFit; + } + if (resizeMode) { + if (!loggedResizeModeDeprecationWarning) { + console.log('[expo-image]: Prop "resizeMode" is deprecated, use "contentFit" instead'); + loggedResizeModeDeprecationWarning = true; + } + + switch (resizeMode) { + case 'contain': + case 'cover': + case 'none': + return resizeMode; + case 'stretch': + return 'fill'; + case 'center': + return 'scale-down'; + case 'repeat': + if (!loggedRepeatDeprecationWarning) { + console.log('[expo-image]: Resize mode "repeat" is no longer supported'); + loggedRepeatDeprecationWarning = true; + } + return 'cover'; + default: { + const exhaustiveCheck: never = resizeMode; + throw new Error(`Unhandled resizeMode case: ${exhaustiveCheck}`); + } + } + } + return 'cover'; +} + +/** + * It resolves a stringified form of the `contentPosition` prop to an object, + * which is the only form supported in the native code. + */ +export function resolveContentPosition( + contentPosition?: ImageContentPosition +): ImageContentPositionObject { + if (typeof contentPosition === 'string') { + const contentPositionStringMappings: Record< + ImageContentPositionString, + ImageContentPositionObject + > = { + center: { top: '50%', left: '50%' }, + top: { top: 0, left: '50%' }, + right: { top: '50%', right: 0 }, + bottom: { bottom: 0, left: '50%' }, + left: { top: '50%', left: 0 }, + 'top center': { top: 0, left: '50%' }, + 'top right': { top: 0, right: 0 }, + 'top left': { top: 0, left: 0 }, + 'right center': { top: '50%', right: 0 }, + 'right top': { top: 0, right: 0 }, + 'right bottom': { bottom: 0, right: 0 }, + 'bottom center': { bottom: 0, left: '50%' }, + 'bottom right': { bottom: 0, right: 0 }, + 'bottom left': { bottom: 0, left: 0 }, + 'left center': { top: '50%', left: 0 }, + 'left top': { top: 0, left: 0 }, + 'left bottom': { bottom: 0, left: 0 }, + }; + const contentPositionObject = contentPositionStringMappings[contentPosition]; + + if (!contentPositionObject) { + console.warn(`[expo-image]: Content position "${contentPosition}" is invalid`); + return contentPositionStringMappings.center; + } + return contentPositionObject; + } + return contentPosition ?? { top: '50%', left: '50%' }; +} + +/** + * If `transition` or `fadeDuration` is a number, it's resolved to a cross dissolve transition with the given duration. + * When `fadeDuration` is used, it logs an appropriate deprecation warning. + */ +export function resolveTransition( + transition?: ImageProps['transition'], + fadeDuration?: ImageProps['fadeDuration'] +): ImageTransition | null { + if (typeof transition === 'number') { + return { duration: transition }; + } + if (!transition && typeof fadeDuration === 'number') { + if (!loggedFadeDurationDeprecationWarning) { + console.warn('[expo-image]: Prop "fadeDuration" is deprecated, use "transition" instead'); + loggedFadeDurationDeprecationWarning = true; + } + return { duration: fadeDuration }; + } + return transition ?? null; +} + +/** + * Checks whether the given value is an instance of the `SharedRef<'image'>` class. + */ +export function isImageRef(value: any): value is SharedRefType<'image'> { + return value instanceof SharedRef && value.nativeRefType === 'image'; +} diff --git a/node_modules/expo-image/src/utils/AssetSourceResolver.web.ts b/node_modules/expo-image/src/utils/AssetSourceResolver.web.ts new file mode 100644 index 00000000..21d37977 --- /dev/null +++ b/node_modules/expo-image/src/utils/AssetSourceResolver.web.ts @@ -0,0 +1,88 @@ +import { PackagerAsset } from '@react-native/assets-registry/registry'; +import { Platform } from 'expo-modules-core'; +import { PixelRatio } from 'react-native'; + +export type ResolvedAssetSource = { + __packager_asset: boolean; + width?: number; + height?: number; + uri: string; + scale: number; +}; + +// Returns the Metro dev server-specific asset location. +function getScaledAssetPath(asset: PackagerAsset): string { + const scale = AssetSourceResolver.pickScale(asset.scales, PixelRatio.get()); + const scaleSuffix = scale === 1 ? '' : '@' + scale + 'x'; + const type = !asset.type ? '' : `.${asset.type}`; + if (__DEV__) { + return asset.httpServerLocation + '/' + asset.name + scaleSuffix + type; + } else { + return asset.httpServerLocation.replace(/\.\.\//g, '_') + '/' + asset.name + scaleSuffix + type; + } +} + +export default class AssetSourceResolver { + serverUrl: string; + // where the jsbundle is being run from + // NOTE(EvanBacon): Never defined on web. + jsbundleUrl?: string | null; + // the asset to resolve + asset: PackagerAsset; + + constructor( + serverUrl: string | undefined | null, + jsbundleUrl: string | undefined | null, + asset: PackagerAsset + ) { + this.serverUrl = serverUrl || 'https://expo.dev'; + this.jsbundleUrl = null; + this.asset = asset; + } + + // Always true for web runtimes + isLoadedFromServer(): boolean { + return true; + } + + // Always false for web runtimes + isLoadedFromFileSystem(): boolean { + return false; + } + + defaultAsset(): ResolvedAssetSource { + return this.assetServerURL(); + } + + /** + * @returns absolute remote URL for the hosted asset. + */ + assetServerURL(): ResolvedAssetSource { + const fromUrl = new URL(getScaledAssetPath(this.asset), this.serverUrl); + fromUrl.searchParams.set('platform', Platform.OS); + fromUrl.searchParams.set('hash', this.asset.hash); + return this.fromSource( + // Relative on web + fromUrl.toString().replace(fromUrl.origin, '') + ); + } + + fromSource(source: string): ResolvedAssetSource { + return { + __packager_asset: true, + width: this.asset.width ?? undefined, + height: this.asset.height ?? undefined, + uri: source, + scale: AssetSourceResolver.pickScale(this.asset.scales, PixelRatio.get()), + }; + } + + static pickScale(scales: number[], deviceScale: number): number { + for (let i = 0; i < scales.length; i++) { + if (scales[i] >= deviceScale) { + return scales[i]; + } + } + return scales[scales.length - 1] || 1; + } +} diff --git a/node_modules/expo-image/src/utils/blurhash/base83.ts b/node_modules/expo-image/src/utils/blurhash/base83.ts new file mode 100644 index 00000000..6d1208ac --- /dev/null +++ b/node_modules/expo-image/src/utils/blurhash/base83.ts @@ -0,0 +1,104 @@ +const digitCharacters = [ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + '#', + '$', + '%', + '*', + '+', + ',', + '-', + '.', + ':', + ';', + '=', + '?', + '@', + '[', + ']', + '^', + '_', + '{', + '|', + '}', + '~', +]; + +export const decode83 = (str: string) => { + let value = 0; + for (let i = 0; i < str.length; i++) { + const c = str[i]; + const digit = digitCharacters.indexOf(c); + value = value * 83 + digit; + } + return value; +}; + +export const encode83 = (n: number, length: number): string => { + let result = ''; + for (let i = 1; i <= length; i++) { + const digit = (Math.floor(n) / Math.pow(83, length - i)) % 83; + result += digitCharacters[Math.floor(digit)]; + } + return result; +}; diff --git a/node_modules/expo-image/src/utils/blurhash/decode.ts b/node_modules/expo-image/src/utils/blurhash/decode.ts new file mode 100644 index 00000000..307f3a38 --- /dev/null +++ b/node_modules/expo-image/src/utils/blurhash/decode.ts @@ -0,0 +1,114 @@ +import { decode83 } from './base83'; +import { ValidationError } from './error'; +import { sRGBToLinear, signPow, linearTosRGB } from './utils'; + +/** + * Returns an error message if invalid or undefined if valid + * @param blurhash + */ +const validateBlurhash = (blurhash: string) => { + if (!blurhash || blurhash.length < 6) { + throw new ValidationError('The blurhash string must be at least 6 characters'); + } + + const sizeFlag = decode83(blurhash[0]); + const numY = Math.floor(sizeFlag / 9) + 1; + const numX = (sizeFlag % 9) + 1; + + if (blurhash.length !== 4 + 2 * numX * numY) { + throw new ValidationError( + `blurhash length mismatch: length is ${blurhash.length} but it should be ${ + 4 + 2 * numX * numY + }` + ); + } +}; + +export const isBlurhashValid = (blurhash: string): { result: boolean; errorReason?: string } => { + try { + validateBlurhash(blurhash); + } catch (error: any) { + return { result: false, errorReason: error.message }; + } + + return { result: true }; +}; + +const decodeDC = (value: number) => { + const intR = value >> 16; + const intG = (value >> 8) & 255; + const intB = value & 255; + return [sRGBToLinear(intR), sRGBToLinear(intG), sRGBToLinear(intB)]; +}; + +const decodeAC = (value: number, maximumValue: number) => { + const quantR = Math.floor(value / (19 * 19)); + const quantG = Math.floor(value / 19) % 19; + const quantB = value % 19; + + const rgb = [ + signPow((quantR - 9) / 9, 2.0) * maximumValue, + signPow((quantG - 9) / 9, 2.0) * maximumValue, + signPow((quantB - 9) / 9, 2.0) * maximumValue, + ]; + + return rgb; +}; + +const decode = (blurhash: string, width: number, height: number, punch?: number) => { + validateBlurhash(blurhash); + + punch = (punch || 1) | 1; + + const sizeFlag = decode83(blurhash[0]); + const numY = Math.floor(sizeFlag / 9) + 1; + const numX = (sizeFlag % 9) + 1; + + const quantisedMaximumValue = decode83(blurhash[1]); + const maximumValue = (quantisedMaximumValue + 1) / 166; + + const colors = new Array(numX * numY); + + for (let i = 0; i < colors.length; i++) { + if (i === 0) { + const value = decode83(blurhash.substring(2, 6)); + colors[i] = decodeDC(value); + } else { + const value = decode83(blurhash.substring(4 + i * 2, 6 + i * 2)); + colors[i] = decodeAC(value, maximumValue * punch); + } + } + + const bytesPerRow = width * 4; + const pixels = new Uint8ClampedArray(bytesPerRow * height); + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + let r = 0; + let g = 0; + let b = 0; + + for (let j = 0; j < numY; j++) { + for (let i = 0; i < numX; i++) { + const basis = Math.cos((Math.PI * x * i) / width) * Math.cos((Math.PI * y * j) / height); + const color = colors[i + j * numX]; + r += color[0] * basis; + g += color[1] * basis; + b += color[2] * basis; + } + } + + const intR = linearTosRGB(r); + const intG = linearTosRGB(g); + const intB = linearTosRGB(b); + + pixels[4 * x + 0 + y * bytesPerRow] = intR; + pixels[4 * x + 1 + y * bytesPerRow] = intG; + pixels[4 * x + 2 + y * bytesPerRow] = intB; + pixels[4 * x + 3 + y * bytesPerRow] = 255; // alpha + } + } + return pixels; +}; + +export default decode; diff --git a/node_modules/expo-image/src/utils/blurhash/error.ts b/node_modules/expo-image/src/utils/blurhash/error.ts new file mode 100644 index 00000000..9910209f --- /dev/null +++ b/node_modules/expo-image/src/utils/blurhash/error.ts @@ -0,0 +1,7 @@ +export class ValidationError extends Error { + constructor(message: string) { + super(message); + this.name = 'ValidationError'; + this.message = message; + } +} diff --git a/node_modules/expo-image/src/utils/blurhash/useBlurhash.tsx b/node_modules/expo-image/src/utils/blurhash/useBlurhash.tsx new file mode 100644 index 00000000..6728e03b --- /dev/null +++ b/node_modules/expo-image/src/utils/blurhash/useBlurhash.tsx @@ -0,0 +1,85 @@ +// adapted from https://gist.github.com/ngbrown/d62eb518753378eb0a9bf02bb4723235 +// modified from https://gist.github.com/WorldMaker/a3cbe0059acd827edee568198376b95a +// https://github.com/woltapp/react-blurhash/issues/3 + +import { useEffect, useState, useMemo } from 'react'; + +import decode from './decode'; +import { isBlurhashString } from '../resolveSources'; + +const DEFAULT_SIZE = { + width: 32, + height: 32, +}; + +// We scale up the canvas to avoid an irritating visual glitch when animating in Chrome. +const scaleRatio = 10; + +export function useBlurhash( + blurhash: { uri?: string; width?: number | null; height?: number | null } | undefined | null, + punch: number = 1 +) { + punch = punch || 1; + + const [uri, setUri] = useState(null); + const isBlurhash = (blurhash?.uri && isBlurhashString(blurhash.uri)) ?? false; + useEffect(() => { + let isCanceled = false; + + if (!blurhash || !blurhash.uri || !isBlurhash) { + return; + } + const strippedBlurhashString = blurhash.uri.replace(/blurhash:\//, ''); + + const pixels = decode( + strippedBlurhashString, + blurhash.width ?? DEFAULT_SIZE.width, + blurhash.height ?? DEFAULT_SIZE.height, + punch + ); + + const canvas = document.createElement('canvas'); + const upscaledCanvas = document.createElement('canvas'); + canvas.width = blurhash.width ?? DEFAULT_SIZE.width; + canvas.height = blurhash.height ?? DEFAULT_SIZE.height; + upscaledCanvas.width = (blurhash.width ?? DEFAULT_SIZE.width) * scaleRatio; + upscaledCanvas.height = (blurhash.height ?? DEFAULT_SIZE.height) * scaleRatio; + const context = canvas.getContext('2d'); + if (!context) { + console.warn('Failed to decode blurhash'); + return; + } + const imageData = context.createImageData(canvas.width, canvas.height); + imageData.data.set(pixels); + context.putImageData(imageData, 0, 0); + const upscaledContext = upscaledCanvas.getContext('2d'); + if (!upscaledContext) { + console.warn('Failed to decode blurhash'); + return; + } + upscaledContext.scale(scaleRatio, scaleRatio); + upscaledContext.drawImage(canvas, 0, 0); + upscaledCanvas.toBlob((blob) => { + if (!isCanceled) { + setUri((oldUrl) => { + if (oldUrl) { + URL.revokeObjectURL(oldUrl); + } + return blob ? URL.createObjectURL(blob) : oldUrl; + }); + } + }); + + return function cleanupBlurhash() { + isCanceled = true; + setUri((oldUrl) => { + if (oldUrl) { + URL.revokeObjectURL(oldUrl); + } + return null; + }); + }; + }, [blurhash?.uri, blurhash?.height, blurhash?.width, punch, isBlurhash]); + const source = useMemo(() => (uri ? { uri } : null), [uri]); + return [source, isBlurhash] as const; +} diff --git a/node_modules/expo-image/src/utils/blurhash/utils.ts b/node_modules/expo-image/src/utils/blurhash/utils.ts new file mode 100644 index 00000000..e87040a6 --- /dev/null +++ b/node_modules/expo-image/src/utils/blurhash/utils.ts @@ -0,0 +1,21 @@ +export const sRGBToLinear = (value: number) => { + const v = value / 255; + if (v <= 0.04045) { + return v / 12.92; + } else { + return Math.pow((v + 0.055) / 1.055, 2.4); + } +}; + +export const linearTosRGB = (value: number) => { + const v = Math.max(0, Math.min(1, value)); + if (v <= 0.0031308) { + return Math.trunc(v * 12.92 * 255 + 0.5); + } else { + return Math.trunc((1.055 * Math.pow(v, 1 / 2.4) - 0.055) * 255 + 0.5); + } +}; + +export const sign = (n: number) => (n < 0 ? -1 : 1); + +export const signPow = (val: number, exp: number) => sign(val) * Math.pow(Math.abs(val), exp); diff --git a/node_modules/expo-image/src/utils/resolveAssetSource.tsx b/node_modules/expo-image/src/utils/resolveAssetSource.tsx new file mode 100644 index 00000000..a3479955 --- /dev/null +++ b/node_modules/expo-image/src/utils/resolveAssetSource.tsx @@ -0,0 +1,3 @@ +import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; + +export default resolveAssetSource; diff --git a/node_modules/expo-image/src/utils/resolveAssetSource.web.ts b/node_modules/expo-image/src/utils/resolveAssetSource.web.ts new file mode 100644 index 00000000..6697486e --- /dev/null +++ b/node_modules/expo-image/src/utils/resolveAssetSource.web.ts @@ -0,0 +1,40 @@ +import { getAssetByID } from '@react-native/assets-registry/registry'; + +import AssetSourceResolver, { ResolvedAssetSource } from './AssetSourceResolver.web'; + +let _customSourceTransformer: undefined | ((resolver: AssetSourceResolver) => ResolvedAssetSource); + +export function setCustomSourceTransformer( + transformer: (resolver: AssetSourceResolver) => ResolvedAssetSource +): void { + _customSourceTransformer = transformer; +} + +/** + * `source` is either a number (opaque type returned by require('./foo.png')) + * or an `ImageSource` like { uri: '' } + */ +export default function resolveAssetSource(source: any): ResolvedAssetSource | undefined { + if (typeof source === 'object') { + return source; + } + + const asset = getAssetByID(source); + if (!asset) { + return undefined; + } + + const resolver = new AssetSourceResolver('https://expo.dev', null, asset); + if (_customSourceTransformer) { + return _customSourceTransformer(resolver); + } + return resolver.defaultAsset(); +} + +Object.defineProperty(resolveAssetSource, 'setCustomSourceTransformer', { + get() { + return setCustomSourceTransformer; + }, +}); + +export const { pickScale } = AssetSourceResolver; diff --git a/node_modules/expo-image/src/utils/resolveHashString.tsx b/node_modules/expo-image/src/utils/resolveHashString.tsx new file mode 100644 index 00000000..7a92add5 --- /dev/null +++ b/node_modules/expo-image/src/utils/resolveHashString.tsx @@ -0,0 +1,36 @@ +import { ImageSource } from '../Image.types'; + +type ImageHashType = 'blurhash' | 'thumbhash'; + +function hashToUri(type: ImageHashType, hash: string): string { + const encodedBlurhash = encodeURI(hash).replace(/#/g, '%23').replace(/\?/g, '%3F'); + return `${type}:/${encodedBlurhash}`; +} + +/** + * Converts a blurhash string (`blurhash:///` or //) into an `ImageSource`. + * + * @return An ImageSource representing the provided blurhash. + * */ +export function resolveBlurhashString(str: string): ImageSource { + const [blurhash, width, height] = str.replace(/^blurhash:\//, '').split('/'); + return { + uri: hashToUri('blurhash', blurhash), + width: parseInt(width, 10) || 16, + height: parseInt(height, 10) || 16, + }; +} + +/** + * Converts a thumbhash string (`thumbhash:/` or ``) into an `ImageSource`. + * + * @return An ImageSource representing the provided thumbhash. + * */ +export function resolveThumbhashString(str: string): ImageSource { + // ThumbHash may contain slashes that could break the url when the slash is at the beginning. + // We replace slashes with backslashes to make sure we don't break the url's path. + const thumbhash = str.replace(/^thumbhash:\//, '').replace(/\//g, '\\'); + return { + uri: hashToUri('thumbhash', thumbhash), + }; +} diff --git a/node_modules/expo-image/src/utils/resolveHashString.web.tsx b/node_modules/expo-image/src/utils/resolveHashString.web.tsx new file mode 100644 index 00000000..c0038f70 --- /dev/null +++ b/node_modules/expo-image/src/utils/resolveHashString.web.tsx @@ -0,0 +1,31 @@ +import { ImageSource } from '../Image.types'; + +/** + * Converts a string in blurhash format (`blurhash:///` + * or //) into an `ImageSource`. + * + * @return An ImageSource representing the provided blurhash. + * */ +export function resolveBlurhashString(str: string): ImageSource { + const [hash, width, height] = str.replace(/^blurhash:\//, '').split('/'); + return { + uri: 'blurhash:/' + hash, + width: parseInt(width, 10) || 16, + height: parseInt(height, 10) || 16, + }; +} + +/** + * Converts a string in thumbhash format (`thumbhash:/` or ``) + * into an `ImageSource`. + * Note: Unlike the `resolveBlurhashString` the `thumbhash:/` scheme has to be present, + * as the scheme has to be explicitly stated to be interpreted a `thumbhash` source. + * + * @return An ImageSource representing the provided thumbhash. + * */ +export function resolveThumbhashString(str: string): ImageSource { + const hash = str.replace(/^thumbhash:\//, ''); + return { + uri: 'thumbhash:/' + hash, + }; +} diff --git a/node_modules/expo-image/src/utils/resolveSources.tsx b/node_modules/expo-image/src/utils/resolveSources.tsx new file mode 100644 index 00000000..86bd02ae --- /dev/null +++ b/node_modules/expo-image/src/utils/resolveSources.tsx @@ -0,0 +1,58 @@ +import { Platform } from 'expo-modules-core'; + +import resolveAssetSource from './resolveAssetSource'; +import { resolveBlurhashString, resolveThumbhashString } from './resolveHashString'; +import { ImageNativeProps, ImageProps, ImageSource } from '../Image.types'; +import { isImageRef } from '../utils'; + +export function isBlurhashString(str: string): boolean { + return /^(blurhash:\/)+[\w#$%*+,\-.:;=?@[\]^_{}|~]+(\/[\d.]+)*$/.test(str); +} + +// Base64 strings will be recognized as blurhash by default (to keep compatibility), +// interpret as thumbhash only if correct uri scheme is provided +export function isThumbhashString(str: string): boolean { + return str.startsWith('thumbhash:/'); +} + +export function resolveSource(source?: ImageSource | string | number | null): ImageSource | null { + if (typeof source === 'string') { + if (isBlurhashString(source)) { + return resolveBlurhashString(source); + } else if (isThumbhashString(source)) { + return resolveThumbhashString(source); + } + return { uri: source }; + } + if (typeof source === 'number') { + return resolveAssetSource(source); + } + if (typeof source === 'object' && (source?.blurhash || source?.thumbhash)) { + const { blurhash, thumbhash, ...restSource } = source; + const resolved = thumbhash + ? resolveThumbhashString(thumbhash) + : resolveBlurhashString(blurhash as string); + return { + ...resolved, + ...restSource, + }; + } + return source ?? null; +} + +/** + * Resolves provided `source` prop to an array of objects expected by the native implementation. + */ +export function resolveSources(sources?: ImageProps['source']): ImageNativeProps['source'] { + if (Array.isArray(sources)) { + return sources.map(resolveSource).filter(Boolean) as ImageSource[]; + } + if (isImageRef(sources)) { + if (Platform.OS === 'web') { + return sources; + } + // @ts-expect-error + return sources.__expo_shared_object_id__; + } + return [resolveSource(sources)].filter(Boolean) as ImageSource[]; +} diff --git a/node_modules/expo-image/src/utils/thumbhash/thumbhash.ts b/node_modules/expo-image/src/utils/thumbhash/thumbhash.ts new file mode 100644 index 00000000..14c83432 --- /dev/null +++ b/node_modules/expo-image/src/utils/thumbhash/thumbhash.ts @@ -0,0 +1,387 @@ +// Code copied and slightly modified from https://github.com/evanw/thumbhash + +/** + * Encodes an RGBA image to a ThumbHash. RGB should not be premultiplied by A. + * + * @param w The width of the input image. Must be โ‰ค100px. + * @param h The height of the input image. Must be โ‰ค100px. + * @param rgba The pixels in the input image, row-by-row. Must have w*h*4 elements. + * @returns The ThumbHash as a Uint8Array. + */ + +export function rgbaToThumbHash(w: number, h: number, rgba: Uint8Array) { + // Encoding an image larger than 100x100 is slow with no benefit + if (w > 100 || h > 100) throw new Error(`${w}x${h} doesn't fit in 100x100`); + const { PI, round, max, cos, abs } = Math; + + // Determine the average color + let avg_r = 0, + avg_g = 0, + avg_b = 0, + avg_a = 0; + for (let i = 0, j = 0; i < w * h; i++, j += 4) { + const alpha = rgba[j + 3] / 255; + avg_r += (alpha / 255) * rgba[j]; + avg_g += (alpha / 255) * rgba[j + 1]; + avg_b += (alpha / 255) * rgba[j + 2]; + avg_a += alpha; + } + if (avg_a) { + avg_r /= avg_a; + avg_g /= avg_a; + avg_b /= avg_a; + } + + const hasAlpha = avg_a < w * h; + const l_limit = hasAlpha ? 5 : 7; // Use fewer luminance bits if there's alpha + const lx = max(1, round((l_limit * w) / max(w, h))); + const ly = max(1, round((l_limit * h) / max(w, h))); + const l: number[] = []; // luminance + const p: number[] = []; // yellow - blue + const q: number[] = []; // red - green + const a: number[] = []; // alpha + + // Convert the image from RGBA to LPQA (composite atop the average color) + for (let i = 0, j = 0; i < w * h; i++, j += 4) { + const alpha = rgba[j + 3] / 255; + const r = avg_r * (1 - alpha) + (alpha / 255) * rgba[j]; + const g = avg_g * (1 - alpha) + (alpha / 255) * rgba[j + 1]; + const b = avg_b * (1 - alpha) + (alpha / 255) * rgba[j + 2]; + l[i] = (r + g + b) / 3; + p[i] = (r + g) / 2 - b; + q[i] = r - g; + a[i] = alpha; + } + + // Encode using the DCT into DC (constant) and normalized AC (varying) terms + const encodeChannel = (channel: number[], nx: number, ny: number) => { + let dc = 0; + const ac: number[] = []; + let scale = 0; + const fx: number[] = []; + for (let cy = 0; cy < ny; cy++) { + for (let cx = 0; cx * ny < nx * (ny - cy); cx++) { + let f = 0; + for (let x = 0; x < w; x++) fx[x] = cos((PI / w) * cx * (x + 0.5)); + for (let y = 0; y < h; y++) + for (let x = 0, fy = cos((PI / h) * cy * (y + 0.5)); x < w; x++) + f += channel[x + y * w] * fx[x] * fy; + f /= w * h; + if (cx || cy) { + ac.push(f); + scale = max(scale, abs(f)); + } else { + dc = f; + } + } + } + if (scale) for (let i = 0; i < ac.length; i++) ac[i] = 0.5 + (0.5 / scale) * ac[i]; + return [dc, ac, scale]; + }; + const [l_dc, l_ac, l_scale] = encodeChannel(l, max(3, lx), max(3, ly)); + const [p_dc, p_ac, p_scale] = encodeChannel(p, 3, 3); + const [q_dc, q_ac, q_scale] = encodeChannel(q, 3, 3); + const [a_dc, a_ac, a_scale] = hasAlpha ? encodeChannel(a, 5, 5) : []; + + // Write the constants + const isLandscape = w > h; + const header24 = + round(63 * (l_dc as number)) | + (round(31.5 + 31.5 * (p_dc as number)) << 6) | + (round(31.5 + 31.5 * (q_dc as number)) << 12) | + (round(31 * (l_scale as number)) << 18) | + ((hasAlpha ? 1 : 0) << 23); + const header16 = + (isLandscape ? ly : lx) | + (round(63 * (p_scale as number)) << 3) | + (round(63 * (q_scale as number)) << 9) | + ((isLandscape ? 1 : 0) << 15); + const hash = [ + header24 & 255, + (header24 >> 8) & 255, + header24 >> 16, + header16 & 255, + header16 >> 8, + ]; + const ac_start = hasAlpha ? 6 : 5; + let ac_index = 0; + if (hasAlpha) hash.push(round(15 * (a_dc as number)) | (round(15 * (a_scale as number)) << 4)); + + // Write the varying factors + for (const ac of hasAlpha ? [l_ac, p_ac, q_ac, a_ac] : [l_ac, p_ac, q_ac]) + for (const f of ac as number[]) + hash[ac_start + (ac_index >> 1)] |= round(15 * f) << ((ac_index++ & 1) << 2); + return new Uint8Array(hash); +} + +/** + * Decodes a ThumbHash to an RGBA image. RGB is not be premultiplied by A. + * + * @param hash The bytes of the ThumbHash. + * @returns The width, height, and pixels of the rendered placeholder image. + */ +export function thumbHashToRGBA(hash: Uint8Array) { + const { PI, min, max, cos, round } = Math; + + // Read the constants + const header24 = hash[0] | (hash[1] << 8) | (hash[2] << 16); + const header16 = hash[3] | (hash[4] << 8); + const l_dc = (header24 & 63) / 63; + const p_dc = ((header24 >> 6) & 63) / 31.5 - 1; + const q_dc = ((header24 >> 12) & 63) / 31.5 - 1; + const l_scale = ((header24 >> 18) & 31) / 31; + const hasAlpha = header24 >> 23; + const p_scale = ((header16 >> 3) & 63) / 63; + const q_scale = ((header16 >> 9) & 63) / 63; + const isLandscape = header16 >> 15; + const lx = max(3, isLandscape ? (hasAlpha ? 5 : 7) : header16 & 7); + const ly = max(3, isLandscape ? header16 & 7 : hasAlpha ? 5 : 7); + const a_dc = hasAlpha ? (hash[5] & 15) / 15 : 1; + const a_scale = (hash[5] >> 4) / 15; + + // Read the varying factors (boost saturation by 1.25x to compensate for quantization) + const ac_start = hasAlpha ? 6 : 5; + let ac_index = 0; + const decodeChannel = (nx: number, ny: number, scale: number) => { + const ac: number[] = []; + for (let cy = 0; cy < ny; cy++) + for (let cx = cy ? 0 : 1; cx * ny < nx * (ny - cy); cx++) + ac.push( + (((hash[ac_start + (ac_index >> 1)] >> ((ac_index++ & 1) << 2)) & 15) / 7.5 - 1) * scale + ); + return ac; + }; + const l_ac = decodeChannel(lx, ly, l_scale); + const p_ac = decodeChannel(3, 3, p_scale * 1.25); + const q_ac = decodeChannel(3, 3, q_scale * 1.25); + const a_ac = hasAlpha ? decodeChannel(5, 5, a_scale) : null; + + // Decode using the DCT into RGB + const ratio = thumbHashToApproximateAspectRatio(hash); + const w = round(ratio > 1 ? 32 : 32 * ratio); + const h = round(ratio > 1 ? 32 / ratio : 32); + const rgba = new Uint8Array(w * h * 4), + fx: number[] = [], + fy: number[] = []; + for (let y = 0, i = 0; y < h; y++) { + for (let x = 0; x < w; x++, i += 4) { + let l = l_dc, + p = p_dc, + q = q_dc, + a = a_dc; + + // Precompute the coefficients + for (let cx = 0, n = max(lx, hasAlpha ? 5 : 3); cx < n; cx++) + fx[cx] = cos((PI / w) * (x + 0.5) * cx); + for (let cy = 0, n = max(ly, hasAlpha ? 5 : 3); cy < n; cy++) + fy[cy] = cos((PI / h) * (y + 0.5) * cy); + + // Decode L + for (let cy = 0, j = 0; cy < ly; cy++) + for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx * ly < lx * (ly - cy); cx++, j++) + l += l_ac[j] * fx[cx] * fy2; + + // Decode P and Q + for (let cy = 0, j = 0; cy < 3; cy++) { + for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx < 3 - cy; cx++, j++) { + const f = fx[cx] * fy2; + p += p_ac[j] * f; + q += q_ac[j] * f; + } + } + + // Decode A + if (hasAlpha) + for (let cy = 0, j = 0; cy < 5; cy++) + for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx < 5 - cy; cx++, j++) + a += a_ac![j] * fx[cx] * fy2; + + // Convert to RGB + const b = l - (2 / 3) * p; + const r = (3 * l - b + q) / 2; + const g = r - q; + rgba[i] = max(0, 255 * min(1, r)); + rgba[i + 1] = max(0, 255 * min(1, g)); + rgba[i + 2] = max(0, 255 * min(1, b)); + rgba[i + 3] = max(0, 255 * min(1, a)); + } + } + return { w, h, rgba }; +} + +/** + * Extracts the average color from a ThumbHash. RGB is not be premultiplied by A. + * + * @param hash The bytes of the ThumbHash. + * @returns The RGBA values for the average color. Each value ranges from 0 to 1. + */ +export function thumbHashToAverageRGBA(hash: Uint8Array) { + const { min, max } = Math; + const header = hash[0] | (hash[1] << 8) | (hash[2] << 16); + const l = (header & 63) / 63; + const p = ((header >> 6) & 63) / 31.5 - 1; + const q = ((header >> 12) & 63) / 31.5 - 1; + const hasAlpha = header >> 23; + const a = hasAlpha ? (hash[5] & 15) / 15 : 1; + const b = l - (2 / 3) * p; + const r = (3 * l - b + q) / 2; + const g = r - q; + return { + r: max(0, min(1, r)), + g: max(0, min(1, g)), + b: max(0, min(1, b)), + a, + }; +} + +/** + * Extracts the approximate aspect ratio of the original image. + * + * @param hash The bytes of the ThumbHash. + * @returns The approximate aspect ratio (i.e. width / height). + */ +export function thumbHashToApproximateAspectRatio(hash: Uint8Array) { + const header = hash[3]; + const hasAlpha = hash[2] & 0x80; + const isLandscape = hash[4] & 0x80; + const lx = isLandscape ? (hasAlpha ? 5 : 7) : header & 7; + const ly = isLandscape ? header & 7 : hasAlpha ? 5 : 7; + return lx / ly; +} + +/** + * Encodes an RGBA image to a PNG data URL. RGB should not be premultiplied by + * A. This is optimized for speed and simplicity and does not optimize for size + * at all. This doesn't do any compression (all values are stored uncompressed). + * + * @param w The width of the input image. Must be โ‰ค100px. + * @param h The height of the input image. Must be โ‰ค100px. + * @param rgba The pixels in the input image, row-by-row. Must have w*h*4 elements. + * @returns A data URL containing a PNG for the input image. + */ +export function rgbaToDataURL(w: number, h: number, rgba: Uint8Array) { + const row = w * 4 + 1; + const idat = 6 + h * (5 + row); + const bytes = [ + 137, + 80, + 78, + 71, + 13, + 10, + 26, + 10, + 0, + 0, + 0, + 13, + 73, + 72, + 68, + 82, + 0, + 0, + w >> 8, + w & 255, + 0, + 0, + h >> 8, + h & 255, + 8, + 6, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + idat >>> 24, + (idat >> 16) & 255, + (idat >> 8) & 255, + idat & 255, + 73, + 68, + 65, + 84, + 120, + 1, + ]; + const table = [ + 0, 498536548, 997073096, 651767980, 1994146192, 1802195444, 1303535960, 1342533948, -306674912, + -267414716, -690576408, -882789492, -1687895376, -2032938284, -1609899400, -1111625188, + ]; + let a = 1, + b = 0; + for (let y = 0, i = 0, end = row - 1; y < h; y++, end += row - 1) { + bytes.push(y + 1 < h ? 0 : 1, row & 255, row >> 8, ~row & 255, (row >> 8) ^ 255, 0); + for (b = (b + a) % 65521; i < end; i++) { + const u = rgba[i] & 255; + bytes.push(u); + a = (a + u) % 65521; + b = (b + a) % 65521; + } + } + bytes.push( + b >> 8, + b & 255, + a >> 8, + a & 255, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 73, + 69, + 78, + 68, + 174, + 66, + 96, + 130 + ); + for (let [start, end] of [ + [12, 29], + [37, 41 + idat], + ]) { + let c = ~0; + for (let i = start; i < end; i++) { + c ^= bytes[i]; + c = (c >>> 4) ^ table[c & 15]; + c = (c >>> 4) ^ table[c & 15]; + } + c = ~c; + bytes[end++] = c >>> 24; + bytes[end++] = (c >> 16) & 255; + bytes[end++] = (c >> 8) & 255; + bytes[end++] = c & 255; + } + return 'data:image/png;base64,' + btoa(String.fromCharCode(...bytes)); +} + +/** + * Decodes a ThumbHash to a PNG data URL. This is a convenience function that + * just calls "thumbHashToRGBA" followed by "rgbaToDataURL". + * + * @param hash The bytes of the ThumbHash. + * @returns A data URL containing a PNG for the rendered ThumbHash. + */ +export function thumbHashToDataURL(hash: Uint8Array): string { + const image = thumbHashToRGBA(hash); + return rgbaToDataURL(image.w, image.h, image.rgba); +} + +/** + * Convenience function added to the original thumbhash code, allows generating a thumbhash image directly from + * thumbhash string. + * @param thumbhash string from which thumbhashDataURL should be generated + * @returns A data URL containing a PNG for the rendered ThumbHash + */ +export function thumbHashStringToDataURL(thumbhash: string): string { + const hash = Uint8Array.from(atob(thumbhash), (c) => c.charCodeAt(0)); + return thumbHashToDataURL(hash); +} diff --git a/node_modules/expo-image/src/web/AnimationManager.tsx b/node_modules/expo-image/src/web/AnimationManager.tsx new file mode 100644 index 00000000..735ee85b --- /dev/null +++ b/node_modules/expo-image/src/web/AnimationManager.tsx @@ -0,0 +1,231 @@ +import React from 'react'; + +import { ImageTransition } from '../Image.types'; + +type Callbacks = { + onReady?: (() => void) | null; + onAnimationFinished?: (() => void) | null; + onMount?: (() => void) | null; + onError?: (() => void) | null; +}; + +export type AnimationManagerNode = [ + key: string, + renderFunction: ( + renderProps: NonNullable + ) => (className: string, style: React.CSSProperties) => React.ReactElement, +]; + +const SUPPORTED_ANIMATIONS: ImageTransition['effect'][] = [ + 'cross-dissolve', + 'flip-from-left', + 'flip-from-right', + 'flip-from-top', + 'flip-from-bottom', +]; + +type NodeStatus = 'mounted' | 'in' | 'active' | 'out' | 'errored'; + +function useAnimationManagerNode(node: AnimationManagerNode | null, initialStatus?: NodeStatus) { + const newNode = React.useMemo(() => { + if (!node) { + return null; + } + const [animationKey, renderFunction] = node; + // key, ReactElement, ref, callbacks + return { + animationKey, + persistedElement: renderFunction, + status: (initialStatus || 'mounted') as NodeStatus, + }; + }, [node?.[0]]); + return newNode; +} + +function validateTimingFunctionForAnimation( + animationClass: ImageTransition['effect'], + timingFunction: ImageTransition['timing'] +) { + if (animationClass?.includes('flip')) { + if (timingFunction?.includes('ease')) { + return 'ease-in-out'; + } + return 'linear'; + } + return timingFunction || null; +} + +function validateAnimationClass(effect: ImageTransition['effect']) { + if (SUPPORTED_ANIMATIONS.includes(effect)) { + return effect; + } + return 'cross-dissolve'; +} + +export function getAnimatorFromTransition(transition: ImageTransition | null | undefined) { + if (!transition?.duration) { + return null; + } + const animationClass = validateAnimationClass(transition.effect); + if (!animationClass) { + return { + startingClass: '', + animateInClass: '', + animateOutClass: '', + containerClass: '', + timingFunction: 'linear', + animationClass: '', + duration: 0, + }; + } + + const timingFunction = validateTimingFunctionForAnimation(animationClass, transition.timing); + const timingClass = `image-timing-${timingFunction}`; + + return { + startingClass: `${animationClass}-start`, + animateInClass: [animationClass, 'transitioning', `${animationClass}-active`, timingClass].join( + ' ' + ), + animateOutClass: [animationClass, `${animationClass}-end`, timingClass].join(' '), + containerClass: `${animationClass}-container`, + timingFunction, + animationClass, + duration: transition?.duration || 0, + }; +} + +type MountedAnimationNode = { + animationKey: string; + persistedElement: ( + renderProps: Callbacks + ) => (className: string, style: React.CSSProperties) => React.ReactElement; + status: NodeStatus; +}; + +export default function AnimationManager({ + children: renderFunction, + initial, + transition, + recyclingKey, +}: { + children: AnimationManagerNode; + initial: AnimationManagerNode | null; + transition: ImageTransition | null | undefined; + recyclingKey?: string | null | undefined; +}) { + const animation = getAnimatorFromTransition(transition); + + const initialNode = useAnimationManagerNode(initial, 'active'); + + const [nodes, setNodes] = React.useState( + initialNode ? [initialNode] : [] + ); + + const [prevRecyclingKey, setPrevRecyclingKey] = React.useState(recyclingKey ?? ''); + if (prevRecyclingKey !== (recyclingKey ?? '')) { + setPrevRecyclingKey(recyclingKey ?? ''); + setNodes(initialNode ? [initialNode] : []); + } + + const removeAllNodesOfKeyExceptShowing = (key?: string) => { + setNodes((n) => + n.filter( + (node) => + (key ? node.animationKey !== key : false) || + node.status === 'in' || + node.status === 'active' + ) + ); + }; + + const newNode = useAnimationManagerNode(renderFunction); + + React.useEffect(() => { + setNodes((n) => { + if (!newNode) { + return n; + } + const existingNodeIndex = n.findIndex((node) => node.animationKey === newNode.animationKey); + if (existingNodeIndex >= 0) { + if (animation) { + return n.map((n2) => + n2.animationKey === newNode.animationKey + ? { ...newNode, status: 'in' } + : { ...n2, status: 'out' } + ); + } else { + return [{ ...newNode, status: 'in' }]; + } + } + return [...n, newNode]; + }); + }, [newNode]); + + function wrapNodeWithCallbacks(node: MountedAnimationNode) { + if (renderFunction[0] === node.animationKey) { + return renderFunction[1]({ + onReady: () => { + if (animation) { + setNodes((nodes) => + nodes.map((n) => (n === newNode ? { ...n, status: 'in' } : { ...n, status: 'out' })) + ); + } else { + setNodes([{ ...node, status: 'in' }]); + } + }, + onAnimationFinished: () => { + setNodes([{ ...node, status: 'in' }]); + }, + onError: () => { + setNodes((nodes) => nodes.map((n) => (n === node ? { ...n, status: 'errored' } : n))); + }, + }); + } + if (initial?.[0] === node.animationKey) { + return initial[1]({ + onAnimationFinished: () => { + if (node.status === 'out') { + removeAllNodesOfKeyExceptShowing(node.animationKey); + } + }, + onError: () => { + setNodes((nodes) => nodes.map((n) => (n === node ? { ...n, status: 'errored' } : n))); + }, + }); + } + return node.persistedElement({ + onAnimationFinished: () => { + removeAllNodesOfKeyExceptShowing(node.animationKey); + }, + }); + } + const styles = { + transitionDuration: `${animation?.duration || 0}ms`, + transitionTimingFunction: animation?.timingFunction || 'linear', + }; + const classes = { + in: animation?.animateInClass, + out: animation?.animateOutClass, + mounted: animation?.startingClass, + }; + + return ( + <> + {[...nodes] + .filter((n) => n.status !== 'errored') + .map((n) => { + const status = n.status as keyof typeof classes & NodeStatus; + // TODO(@kitten): This creates impossible states! + // Ensure that the above type is either exhaustively reflected in this `map` so `className` sheds `undefined`, + // or retype the `MountedAnimationNode` function to accept `className: string | undefined` + const className = classes[status]!; + return ( +

+ {wrapNodeWithCallbacks(n)(className, styles)} +
+ ); + })} + + ); +} diff --git a/node_modules/expo-image/src/web/ColorTintFilter.tsx b/node_modules/expo-image/src/web/ColorTintFilter.tsx new file mode 100644 index 00000000..92420154 --- /dev/null +++ b/node_modules/expo-image/src/web/ColorTintFilter.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { StyleSheet } from 'react-native'; + +export function getTintColorStyle(tintId: string, tintColor?: string | null) { + if (!tintColor) { + return {}; + } + return { + filter: `url(#expo-image-tint-${tintId})`, + }; +} + +type TintColorFilterProps = { id: string; tintColor?: string | null }; + +export default function TintColorFilter({ id, tintColor }: TintColorFilterProps) { + if (!tintColor) { + return null; + } + return ( + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + svg: { + width: 0, + height: 0, + }, +}); diff --git a/node_modules/expo-image/src/web/ImageRef.ts b/node_modules/expo-image/src/web/ImageRef.ts new file mode 100644 index 00000000..648ad12b --- /dev/null +++ b/node_modules/expo-image/src/web/ImageRef.ts @@ -0,0 +1,24 @@ +import { SharedRef } from 'expo'; + +import { ImageRef } from '../Image.types'; + +export default class ImageRefWeb extends SharedRef<'image'> implements ImageRef { + override nativeRefType = 'image'; + + uri: string | null = null; + width: number = 0; + height: number = 0; + mediaType: string | null = null; + scale: number = 1; + isAnimated: boolean = false; + + static init(uri: string, width: number, height: number, mediaType: string | null): ImageRefWeb { + return Object.assign(new ImageRefWeb(), { + uri, + width, + height, + mediaType, + isAnimated: mediaType === 'image/gif', + }); + } +} diff --git a/node_modules/expo-image/src/web/ImageWrapper.tsx b/node_modules/expo-image/src/web/ImageWrapper.tsx new file mode 100644 index 00000000..e8f891d5 --- /dev/null +++ b/node_modules/expo-image/src/web/ImageWrapper.tsx @@ -0,0 +1,94 @@ +import React, { useEffect, Ref, useId } from 'react'; + +import ColorTintFilter, { getTintColorStyle } from './ColorTintFilter'; +import { ImageWrapperProps } from './ImageWrapper.types'; +import { getImageWrapperEventHandler } from './getImageWrapperEventHandler'; +import { useHeaders, useImageHashes } from './hooks'; +import { absoluteFilledPosition, getObjectPositionFromContentPositionObject } from './positioning'; +import { SrcSetSource } from './useSourceSelection'; +import { ImageNativeProps, ImageSource } from '../Image.types'; + +function getFetchPriorityFromImagePriority(priority: ImageNativeProps['priority'] = 'normal') { + return priority && ['low', 'high'].includes(priority) ? priority : 'auto'; +} + +function getImgPropsFromSource(source: ImageSource | SrcSetSource | null | undefined) { + if (source && 'srcset' in source) { + return { + srcSet: source.srcset, + sizes: source.sizes, + }; + } + return {}; +} + +const ImageWrapper = React.forwardRef( + ( + { + source, + events, + contentPosition, + hashPlaceholderContentPosition, + priority, + style, + hashPlaceholderStyle, + tintColor, + className, + accessibilityLabel, + cachePolicy, + ...props + }: ImageWrapperProps, + ref: Ref + ) => { + useEffect(() => { + events?.onMount?.forEach((e) => e?.()); + }, []); + + // Use a unique ID for the SVG filter so that multiple can be used + // on the same page with different tint colors without conflicts. + const tintId = useId() + // Make it safe for use as an SVG ID. SVG IDs are most strict than HTML + // IDs. They must be compliant with https://www.w3.org/TR/xml/#NT-Name. + // React 19 changed useId() to include ยซ and ยป. These must be removed or + // the SVG filter will not work (e.g. in Safari which enforces the spec). + .replace(/[ยซยป]/g, '_'); + + // Thumbhash uri always has to start with 'thumbhash:/' + const { resolvedSource, isImageHash } = useImageHashes(source); + const objectPosition = getObjectPositionFromContentPositionObject( + isImageHash ? hashPlaceholderContentPosition : contentPosition + ); + + const sourceWithHeaders = useHeaders(resolvedSource, cachePolicy, events?.onError); + if (!sourceWithHeaders) { + return null; + } + return ( + <> + + {accessibilityLabel} + + ); + } +); + +export default ImageWrapper; diff --git a/node_modules/expo-image/src/web/ImageWrapper.types.ts b/node_modules/expo-image/src/web/ImageWrapper.types.ts new file mode 100644 index 00000000..19bbe2f1 --- /dev/null +++ b/node_modules/expo-image/src/web/ImageWrapper.types.ts @@ -0,0 +1,38 @@ +import { CSSProperties, SyntheticEvent } from 'react'; + +import { SrcSetSource } from './useSourceSelection'; +import { ImageContentPositionObject, ImageProps, ImageSource } from '../Image.types'; + +export type OnErrorEvent = + | (({ source }: { source: ImageSource | null }) => void) + | undefined + | null; +export type OnLoadEvent = + | ((event: SyntheticEvent) => void) + | undefined + | null; +export type OnTransitionEndEvent = (() => void) | undefined | null; +export type OnMountEvent = (() => void) | undefined | null; +export type OnDisplayEvent = (() => void) | undefined | null; + +export type ImageWrapperEvents = { + onLoad?: OnLoadEvent[]; + onError?: OnErrorEvent[]; + onTransitionEnd?: OnTransitionEndEvent[]; + onMount?: OnMountEvent[]; + onDisplay?: OnDisplayEvent[]; +}; + +export type ImageWrapperProps = { + source?: ImageSource | SrcSetSource | null; + events?: ImageWrapperEvents; + contentPosition?: ImageContentPositionObject; + hashPlaceholderContentPosition?: ImageContentPositionObject; + priority?: string | null; + style: CSSProperties; + tintColor?: string | null; + hashPlaceholderStyle?: CSSProperties; + className?: string; + accessibilityLabel?: string; + cachePolicy?: ImageProps['cachePolicy']; +}; diff --git a/node_modules/expo-image/src/web/getImageWrapperEventHandler.ts b/node_modules/expo-image/src/web/getImageWrapperEventHandler.ts new file mode 100644 index 00000000..47c977fe --- /dev/null +++ b/node_modules/expo-image/src/web/getImageWrapperEventHandler.ts @@ -0,0 +1,31 @@ +import type { SyntheticEvent } from 'react'; + +import type { ImageWrapperEvents } from './ImageWrapper.types'; +import type { ImageSource } from '../Image.types'; +import { isBlurhashString } from '../utils/resolveSources'; + +export function getImageWrapperEventHandler( + events: ImageWrapperEvents | undefined, + source: ImageSource +) { + return { + onLoad: (event: SyntheticEvent) => { + events?.onLoad?.forEach((e) => e?.(event)); + + if (typeof window !== 'undefined') { + // On Web there is no way to detect when the image gets displayed, but we can assume it happens on the repaint right after the image is successfully loaded. + window.requestAnimationFrame(() => { + events?.onDisplay?.forEach((e) => e?.()); + }); + } + }, + onTransitionEnd: () => events?.onTransitionEnd?.forEach((e) => e?.()), + onError: () => { + // A temporary workaround for blurhash blobs throwing opaque errors when used in an img tag. + if (source?.uri && isBlurhashString(source?.uri)) { + return; + } + events?.onError?.forEach((e) => e?.({ source: source || null })); + }, + }; +} diff --git a/node_modules/expo-image/src/web/hooks.ts b/node_modules/expo-image/src/web/hooks.ts new file mode 100644 index 00000000..12429b47 --- /dev/null +++ b/node_modules/expo-image/src/web/hooks.ts @@ -0,0 +1,76 @@ +import { useEffect, useMemo, useState } from 'react'; + +import { OnErrorEvent } from './ImageWrapper.types'; +import { ImageNativeProps, ImageSource } from '../Image.types'; +import { useBlurhash } from '../utils/blurhash/useBlurhash'; +import { isThumbhashString } from '../utils/resolveSources'; +import { thumbHashStringToDataURL } from '../utils/thumbhash/thumbhash'; + +export function useThumbhash(source: ImageSource | null | undefined) { + const isThumbhash = isThumbhashString(source?.uri || ''); + const strippedThumbhashString = source?.uri?.replace(/thumbhash:\//, '') ?? ''; + const thumbhashSource = useMemo( + () => (isThumbhash ? { uri: thumbHashStringToDataURL(strippedThumbhashString) } : null), + [strippedThumbhashString, isThumbhash] + ); + return [thumbhashSource, isThumbhash] as const; +} + +export function useImageHashes(source: ImageSource | null | undefined) { + const [thumbhash, isThumbhashString] = useThumbhash(source); + const [blurhash, isBlurhashString] = useBlurhash(source); + return useMemo(() => { + if (!isThumbhashString && !isBlurhashString) { + return { resolvedSource: source, isImageHash: false }; + } + if (!blurhash && !thumbhash) { + return { resolvedSource: null, isImageHash: true }; + } + return { + resolvedSource: blurhash ?? thumbhash, + isImageHash: true, + }; + }, [blurhash, thumbhash, isThumbhashString, isBlurhashString, source]); +} + +export function useHeaders( + source: ImageSource | null | undefined, + cachePolicy: ImageNativeProps['cachePolicy'], + onError?: OnErrorEvent[] +): ImageSource | null | undefined { + const [objectURL, setObjectURL] = useState(null); + useEffect(() => { + (async () => { + if (!source?.headers || !source.uri) { + return; + } + try { + const result = await fetch(source.uri, { + headers: source.headers, + cache: cachePolicy === 'none' ? 'no-cache' : 'default', + redirect: 'follow', + }); + if (!result.ok) { + throw new Error(`Failed to fetch image: ${result.status} ${result.statusText}`); + } + const blob = await result.blob(); + setObjectURL((prevObjURL) => { + if (prevObjURL) { + URL.revokeObjectURL(prevObjURL); + } + return URL.createObjectURL(blob); + }); + } catch { + onError?.forEach((e) => e?.({ source })); + } + })(); + }, [source]); + if (!source?.headers) { + return source; + } + if (!objectURL) { + // Avoid fetching a URL without headers if we have headers + return null; + } + return { ...source, uri: objectURL }; +} diff --git a/node_modules/expo-image/src/web/imageStyles.tsx b/node_modules/expo-image/src/web/imageStyles.tsx new file mode 100644 index 00000000..37a5fbcb --- /dev/null +++ b/node_modules/expo-image/src/web/imageStyles.tsx @@ -0,0 +1,136 @@ +const style = ` +[data-expoimage] .cross-dissolve { + transition-property: opacity; + animation-fill-mode: forwards; +} +[data-expoimage] .cross-dissolve-start:not(.transitioning) { + opacity: 0; +} +[data-expoimage] .cross-dissolve-active { + opacity: 1; +} +[data-expoimage] .cross-dissolve-end { + opacity: 0; +} +[data-expoimage] .flip-from-left { + transition-property: transform, opacity; + transition-timing-function: var(--expo-image-timing,linear), steps(2, jump-none) !important; + transform-origin: center; + +} +[data-expoimage] .flip-from-left-container { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + perspective: 1000px; +} +[data-expoimage] .flip-from-left-start:not(.transitioning) { + transform: translateZ(calc(var(--expo-image-width,1000px) * -1.25)) rotateY(-180deg); + opacity: 0; +} +[data-expoimage] .flip-from-left-active { + transform: translateZ(0px) rotateY(0) ; + opacity:1; +} +[data-expoimage] .flip-from-left-end { + transform: translateZ(calc(var(--expo-image-width,1000px) * -1.25)) rotateY(180deg); + opacity: 0; +} +[data-expoimage] .flip-from-right { + transition-property: transform, opacity; + transition-timing-function: var(--expo-image-timing,linear), steps(2, jump-none) !important; + transform-origin: center; +} +[data-expoimage] .flip-from-right-container { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + perspective: 1000px; +} +[data-expoimage] .flip-from-right-start:not(.transitioning) { + transform: translateZ(calc(var(--expo-image-width,1000px) * -1.25)) rotateY(180deg); + opacity: 0; +} +[data-expoimage] .flip-from-right-active { + transform: translateZ(0px) rotateY(0) ; + opacity:1; +} +[data-expoimage] .flip-from-right-end { + transform: translateZ(calc(var(--expo-image-width,1000px) * -1.25)) rotateY(-180deg); + opacity: 0; +} +[data-expoimage] .flip-from-top { + transition-property: transform, opacity; + transition-timing-function: var(--expo-image-timing,linear), steps(2, jump-none) !important; + transform-origin: center; +} +[data-expoimage] .flip-from-top-container { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + perspective: 1000px; +} +[data-expoimage] .flip-from-top-start:not(.transitioning) { + transform: translateZ(calc(var(--expo-image-height,1000px) * -1.5)) rotateX(180deg); + opacity: 0; +} +[data-expoimage] .flip-from-top-active { + transform: translateZ(0px) rotateX(0) ; + opacity:1; +} +[data-expoimage] .flip-from-top-end { + transform: translateZ(calc(var(--expo-image-height,1000px) * -1.5)) rotateX(-180deg); + opacity: 0; +} +[data-expoimage] .flip-from-bottom { + transition-property: transform, opacity; + transition-timing-function: var(--expo-image-timing,linear), steps(2, jump-none) !important; + transform-origin: center; +} +[data-expoimage] .flip-from-bottom-container { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + perspective: 1000px; +} +[data-expoimage] .flip-from-bottom-start:not(.transitioning) { + transform: translateZ(calc(var(--expo-image-height,1000px) * -1.25)) rotateX(-180deg); + opacity: 0; +} +[data-expoimage] .flip-from-bottom-active { + transform: translateZ(0px) rotateX(0) ; + opacity:1; +} +[data-expoimage] .flip-from-bottom-end { + transform: translateZ(calc(var(--expo-image-height,1000px) * -1.25)) rotateX(180deg); + opacity: 0; +} +[data-expoimage] .image-timing-linear { + --expo-image-timing: linear; +} +[data-expoimage] .image-timing-ease-in { + --expo-image-timing: ease-in; +} +[data-expoimage] .image-timing-ease-out { + --expo-image-timing: ease-out; +} +[data-expoimage] .image-timing-ease-in-out { + --expo-image-timing: ease-in-out; +} +`; +export default function loadStyle() { + if (typeof window !== 'undefined') { + const styleTag = document.createElement('style'); + styleTag.innerHTML = style; + styleTag.id = 'expo-image-styles'; + document.head.appendChild(styleTag); + } +} diff --git a/node_modules/expo-image/src/web/positioning.ts b/node_modules/expo-image/src/web/positioning.ts new file mode 100644 index 00000000..b7d48dad --- /dev/null +++ b/node_modules/expo-image/src/web/positioning.ts @@ -0,0 +1,48 @@ +import { ImageContentPositionObject, ImageContentPositionValue } from '../Image.types'; + +export function ensureValueIsWebUnits(value: string | number) { + const trimmedValue = String(value).trim(); + if (trimmedValue.endsWith('%')) { + return trimmedValue; + } + return `${trimmedValue}px`; +} + +type KeysOfUnion = T extends T ? keyof T : never; + +export const absoluteFilledPosition = { + width: '100%', + height: '100%', + position: 'absolute', + left: 0, + top: 0, +} as const; + +export function getObjectPositionFromContentPositionObject( + contentPosition?: ImageContentPositionObject +): string { + const resolvedPosition = { ...contentPosition } as Record< + KeysOfUnion, + ImageContentPositionValue + >; + if (!resolvedPosition) { + return '50% 50%'; + } + if (resolvedPosition.top == null && resolvedPosition.bottom == null) { + resolvedPosition.top = '50%'; + } + if (resolvedPosition.left == null && resolvedPosition.right == null) { + resolvedPosition.left = '50%'; + } + + return ( + (['top', 'bottom', 'left', 'right'] as const) + .map((key) => { + if (key in resolvedPosition) { + return `${key} ${ensureValueIsWebUnits(resolvedPosition[key])}`; + } + return ''; + }) + .join(' ') || '50% 50%' + ); +} diff --git a/node_modules/expo-image/src/web/useSourceSelection.ts b/node_modules/expo-image/src/web/useSourceSelection.ts new file mode 100644 index 00000000..6a3ba946 --- /dev/null +++ b/node_modules/expo-image/src/web/useSourceSelection.ts @@ -0,0 +1,134 @@ +import type { SharedRefType } from 'expo'; +import React, { useState } from 'react'; + +import { ImageProps, ImageSource } from '../Image.types'; +import { isImageRef } from '../utils'; +import { isBlurhashString, isThumbhashString } from '../utils/resolveSources'; + +function findBestSourceForSize( + sources: ImageSource[] | undefined, + size: DOMRect | null +): ImageSource | null { + if (sources?.length === 1) { + return sources[0]; + } + return ( + [...(sources || [])] + // look for the smallest image that's still larger then a container + ?.map((source) => { + if (!size) { + return { source, penalty: 0, covers: false }; + } + const { width, height } = + typeof source === 'object' ? source : { width: null, height: null }; + if (width == null || height == null) { + return { source, penalty: 0, covers: false }; + } + if (width < size.width || height < size.height) { + return { + source, + penalty: Math.max(size.width - width, size.height - height), + covers: false, + }; + } + return { source, penalty: (width - size.width) * (height - size.height), covers: true }; + }) + .sort((a, b) => a.penalty - b.penalty) + .sort((a, b) => Number(b.covers) - Number(a.covers))[0]?.source ?? null + ); +} + +export interface SrcSetSource extends ImageSource { + srcset: string; + sizes: string; + // used as key and a fallback in case srcset is not supported + uri: string; + type: 'srcset'; +} + +function getCSSMediaQueryForSource(source: ImageSource) { + return `(max-width: ${source.webMaxViewportWidth ?? source.width}px) ${source.width}px`; +} + +function selectSource( + sources: ImageSource[] | undefined, + size: DOMRect | null, + responsivePolicy: ImageProps['responsivePolicy'] +): ImageSource | SrcSetSource | null { + if (sources == null || sources.length === 0) { + return null; + } + + if (sources.length === 1) { + return sources[0]; + } + + if (responsivePolicy !== 'static') { + return findBestSourceForSize(sources, size); + } + const staticSupportedSources = sources + .filter( + (s) => s.uri && s.width != null && !isBlurhashString(s.uri) && !isThumbhashString(s.uri) + ) + .sort( + (a, b) => (a.webMaxViewportWidth ?? a.width ?? 0) - (b.webMaxViewportWidth ?? b.width ?? 0) + ); + + if (staticSupportedSources.length === 0) { + console.warn( + "You've set the `static` responsivePolicy but none of the sources have the `width` properties set. Make sure you set both `width` and `webMaxViewportWidth` for best results when using static responsiveness. Falling back to the `initial` policy." + ); + return findBestSourceForSize(sources, size); + } + + const srcset = staticSupportedSources + ?.map((source) => `${source.uri} ${source.width}w`) + .join(', '); + const sizes = `${staticSupportedSources + ?.map(getCSSMediaQueryForSource) + .join(', ')}, ${staticSupportedSources[staticSupportedSources.length - 1]?.width}px`; + return { + srcset, + sizes, + uri: staticSupportedSources[staticSupportedSources.length - 1]?.uri ?? '', + type: 'srcset', + }; +} + +export default function useSourceSelection( + sources: ImageSource[] | SharedRefType<'image'> | undefined, + responsivePolicy: ImageProps['responsivePolicy'] = 'static', + containerRef: React.RefObject, + measurementCallback: ((target: HTMLElement, size: DOMRect) => void) | null = null +): ImageSource | SrcSetSource | SharedRefType<'image'> | null { + const hasMoreThanOneSource = (Array.isArray(sources) ? sources.length : 0) > 1; + const [size, setSize] = useState( + containerRef.current?.getBoundingClientRect() ?? null + ); + if (size && containerRef.current) { + measurementCallback?.(containerRef.current, size); + } + + React.useEffect(() => { + if ((!hasMoreThanOneSource && !measurementCallback) || !containerRef.current) { + return () => {}; + } + if (responsivePolicy === 'live') { + const resizeObserver = new ResizeObserver((entries) => { + setSize(entries[0].contentRect); + measurementCallback?.(entries[0].target as any, entries[0].contentRect); + }); + resizeObserver.observe(containerRef.current); + return () => { + resizeObserver.disconnect(); + }; + } + return () => {}; + }, [responsivePolicy, hasMoreThanOneSource, containerRef.current, measurementCallback]); + + if (isImageRef(sources)) { + // There is always only one image ref, so there is nothing else to select from. + return sources; + } + return selectSource(sources, size, responsivePolicy); +} diff --git a/node_modules/expo-image/tsconfig.json b/node_modules/expo-image/tsconfig.json new file mode 100644 index 00000000..ae1ae0dd --- /dev/null +++ b/node_modules/expo-image/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "expo-module-scripts/tsconfig.base", + "compilerOptions": { + "outDir": "./build", + "emitDeclarationOnly": true + }, + "include": ["./src"], + "exclude": ["**/__mocks__/*", "**/__tests__/*", "**/__rsc_tests__/*"] +} diff --git a/src/screens/CameraScreen.tsx b/src/screens/CameraScreen.tsx index 64f817c6..9091caf2 100644 --- a/src/screens/CameraScreen.tsx +++ b/src/screens/CameraScreen.tsx @@ -168,7 +168,7 @@ export default function CameraScreen({ const sizes = await expoCameraRef.current.getAvailablePictureSizes(); if (sizes && sizes.length > 0) { console.log('Available camera sizes:', sizes); - const parsedSizes = sizes.map(s => { + const parsedSizes = sizes.map((s: string) => { const parts = s.split('x'); if (parts.length === 2) { const w = parseInt(parts[0], 10); @@ -176,11 +176,11 @@ export default function CameraScreen({ return { size: s, pixels: w * h, w, h }; } return { size: s, pixels: 0, w: 0, h: 0 }; - }).filter(s => s.pixels > 0); + }).filter((s: { pixels: number }) => s.pixels > 0); parsedSizes.sort((a, b) => a.pixels - b.pixels); // Find the smallest size that has at least 1920 width or height (approx 2MP) - const optimal = parsedSizes.find(s => Math.max(s.w, s.h) >= 1920); + const optimal = parsedSizes.find((s: { w: number; h: number }) => Math.max(s.w, s.h) >= 1920); if (optimal) { console.log('Setting optimal pictureSize to prevent OOM:', optimal.size); diff --git a/src/screens/PreviewScreen.tsx b/src/screens/PreviewScreen.tsx index bd0d7193..1e388f29 100644 --- a/src/screens/PreviewScreen.tsx +++ b/src/screens/PreviewScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef, useCallback } from 'react'; +import React, { useState, useRef, useCallback, useEffect } from 'react'; import { StyleSheet, Text,