@amazon-devices/expo-image 2.0.9000000000 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CMakeLists.txt +80 -58
  2. package/README.kepler.md +1 -0
  3. package/build/ExpoImageModules.kepler.d.ts.map +1 -1
  4. package/build/ExpoImageModules.kepler.js +1 -1
  5. package/build/ExpoImageModules.kepler.js.map +1 -1
  6. package/build/ImageViewNativeComponent.js +1 -1
  7. package/build/ImageViewNativeComponent.js.map +1 -1
  8. package/kepler/AutoLinkInit.cpp +17 -0
  9. package/kepler/Constants.h +21 -0
  10. package/kepler/ExpoImage/ExpoImageTurboModule.cpp +148 -0
  11. package/kepler/ExpoImage/ExpoImageTurboModule.h +41 -0
  12. package/kepler/ExpoImage/PrefetchListener.cpp +45 -0
  13. package/kepler/ExpoImage/PrefetchListener.h +40 -0
  14. package/kepler/ExpoImageComponent/AccessibilityDelegate.cpp +33 -0
  15. package/kepler/ExpoImageComponent/AccessibilityDelegate.h +30 -0
  16. package/kepler/ExpoImageComponent/ExpoImageComponentViewManager.cpp +132 -0
  17. package/kepler/ExpoImageComponent/ExpoImageComponentViewManager.h +66 -0
  18. package/kepler/ExpoImageComponent/ExpoImageStore.h +102 -0
  19. package/kepler/ExpoImageComponent/ExpoImageView.cpp +556 -0
  20. package/kepler/ExpoImageComponent/ExpoImageView.h +186 -0
  21. package/kepler/ExpoImageComponent/HashImageDecoder.cpp +91 -0
  22. package/kepler/ExpoImageComponent/HashImageDecoder.h +18 -0
  23. package/kepler/ExpoImageComponent/ImageLoadListeners.cpp +73 -0
  24. package/kepler/ExpoImageComponent/ImageLoadListeners.h +52 -0
  25. package/kepler/ExpoImageComponent/TransitionAnimator.cpp +113 -0
  26. package/kepler/ExpoImageComponent/TransitionAnimator.h +32 -0
  27. package/kepler/ImageSourceInfo.h +21 -0
  28. package/kepler/ViewManagerFactory.cpp +36 -0
  29. package/kepler/ViewManagerFactory.h +29 -0
  30. package/kepler/blurhash/common.h +11 -10
  31. package/kepler/blurhash/decode.cpp +26 -16
  32. package/kepler/contentposition/ImageContentPosition.cpp +50 -69
  33. package/kepler/contentposition/ImageContentPosition.h +36 -26
  34. package/kepler/enums/ImageContentFit.h +47 -15
  35. package/kepler/enums/ImagePriority.h +12 -13
  36. package/kepler/test/blurhash/decode.cpp +36 -27
  37. package/kepler/test/contentposition/ImageContentPosition.cpp +70 -76
  38. package/kepler/test/thumbhash/decode.cpp +18 -10
  39. package/kepler/test/utils/Base64.cpp +73 -0
  40. package/kepler/test/utils/ColorUtils.cpp +53 -0
  41. package/kepler/test/utils/SourceSelection.cpp +87 -0
  42. package/kepler/thumbhash/decode.cpp +7 -16
  43. package/kepler/utils/Base64.cpp +85 -0
  44. package/kepler/utils/Base64.h +36 -0
  45. package/kepler/utils/ColorUtils.h +26 -0
  46. package/kepler/utils/SourceSelection.cpp +80 -0
  47. package/kepler/utils/SourceSelection.h +65 -0
  48. package/lib/aarch64-release/com.amazon.kepler.uitoolkit.react.expoimageapp.so +0 -0
  49. package/lib/aarch64-release/libexpo-image-3.so +0 -0
  50. package/lib/armv7-release/com.amazon.kepler.uitoolkit.react.expoimageapp.so +0 -0
  51. package/lib/armv7-release/libexpo-image-3.so +0 -0
  52. package/lib/x86_64-release/com.amazon.kepler.uitoolkit.react.expoimageapp.so +0 -0
  53. package/lib/x86_64-release/libexpo-image-3.so +0 -0
  54. package/package.json +1 -1
  55. package/react-native.config.js +12 -4
  56. package/src/ExpoImageModules.kepler.ts +1 -1
  57. package/src/ImageViewNativeComponent.ts +1 -1
  58. package/kepler/ExpoImage.cpp +0 -63
  59. package/kepler/ExpoImage.h +0 -29
  60. package/kepler/ExpoImageCacheHandler.cpp +0 -15
  61. package/kepler/ExpoImageCacheHandler.h +0 -28
  62. package/kepler/ExpoImageComponent.cpp +0 -579
  63. package/kepler/ExpoImageComponent.h +0 -199
  64. package/kepler/ImageWrapper/BlurhashImageWrapper.cpp +0 -37
  65. package/kepler/ImageWrapper/BlurhashImageWrapper.h +0 -23
  66. package/kepler/ImageWrapper/CacheImageWrapper.cpp +0 -80
  67. package/kepler/ImageWrapper/CacheImageWrapper.h +0 -41
  68. package/kepler/ImageWrapper/HashImageWrapper.cpp +0 -21
  69. package/kepler/ImageWrapper/HashImageWrapper.h +0 -22
  70. package/kepler/ImageWrapper/ImageWrapper.cpp +0 -24
  71. package/kepler/ImageWrapper/ImageWrapper.h +0 -33
  72. package/kepler/ImageWrapper/LocalImageWrapper.cpp +0 -61
  73. package/kepler/ImageWrapper/LocalImageWrapper.h +0 -30
  74. package/kepler/ImageWrapper/ThumbhashImageWrapper.cpp +0 -33
  75. package/kepler/ImageWrapper/ThumbhashImageWrapper.h +0 -20
  76. package/kepler/KeplerImageViewComponent.h +0 -64
  77. package/kepler/KeplerInit.cpp +0 -35
  78. package/kepler/managers/ExpoImageManager.cpp +0 -38
  79. package/kepler/managers/ExpoImageManager.h +0 -23
  80. package/kepler/test/ExpoImage.cpp +0 -73
  81. package/kepler/test/ExpoImageComponent.cpp +0 -304
  82. package/kepler/test/managers/ExpoImageManager.cpp +0 -39
  83. package/kepler/test/utils/LoaderDelegate.cpp +0 -130
  84. package/kepler/utils/PlaceholderImageLoaderDelegate.cpp +0 -26
  85. package/kepler/utils/PlaceholderImageLoaderDelegate.h +0 -26
  86. package/kepler/utils/PrefetchImageLoaderDelegate.cpp +0 -55
  87. package/kepler/utils/PrefetchImageLoaderDelegate.h +0 -35
  88. package/kepler/utils/SourceImageLoaderDelegate.cpp +0 -47
  89. package/kepler/utils/SourceImageLoaderDelegate.h +0 -42
  90. package/kepler/utils/SourceImageLoaderDelegateEmitter.cpp +0 -15
  91. package/kepler/utils/SourceImageLoaderDelegateEmitter.h +0 -28
  92. package/kepler-compatibility.json +0 -6
@@ -0,0 +1,132 @@
1
+ #include "ExpoImageComponentViewManager.h"
2
+
3
+ #include <apmf/fn.h>
4
+ #include <apmf/iface/com/amazon/apmf/ISubscription.h>
5
+ #include <apmf/iface/com/amazon/apmf/InvalidArgumentError.h>
6
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/react/IJsObjectBuilder.h>
7
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/view/IImageView3.h>
8
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/view/IView2.h>
9
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/view/IViewLifecycleListener.h>
10
+ #include <apmf/log.h>
11
+
12
+ #include <limits>
13
+
14
+ namespace expoimage {
15
+
16
+ using namespace apmf::log_levels;
17
+ using apmf::iface::com::amazon::kepler::uitoolkit::view::IImageView3;
18
+ using apmf::iface::com::amazon::kepler::uitoolkit::view::IView2;
19
+
20
+ APMF_DEFINE_LOGGER(ExpoImageVMLogger, "com.amazon.kepler.uitoolkit.react.expoimage.vm");
21
+
22
+ ExpoImageComponentViewManager::ExpoImageComponentViewManager(View<IViewModule> viewModule) {
23
+ viewModule_ = viewModule;
24
+ // use the single process-wide store (shared loader +
25
+ // decoded-image cache) so every view manager AND the ExpoImage TurboModule's
26
+ // clearMemoryCache() operate on the same cache. The shared loader is created
27
+ // lazily here on first construction.
28
+ store_ = sharedExpoImageStore();
29
+ if (viewModule_ && !store_->loader) {
30
+ store_->loader = viewModule_->makeImageLoader();
31
+ }
32
+ }
33
+
34
+ Ptr<IView> ExpoImageComponentViewManager::createEntry(Ptr<IView> containerPtr) {
35
+ // Hierarchy: containerPtr → clipView (clips descendants) → imageView.
36
+ // clipView is sized to the cell bounds and clips the imageView when it is
37
+ // sized to the image's natural dimensions (Fit::NONE / ScaleDown-small).
38
+ auto clipView = viewModule_->makeView();
39
+ if (!clipView) {
40
+ throw apmf::InvalidArgumentError("ExpoImage: viewModule->makeView() returned null for clipView");
41
+ }
42
+ auto clipView2 = clipView.TryQueryInterface<IView2>();
43
+ if (clipView2) clipView2->setContentClipping(true);
44
+
45
+ auto imageViewBase = viewModule_->makeImageView();
46
+ if (!imageViewBase) {
47
+ throw apmf::InvalidArgumentError("ExpoImage: viewModule->makeImageView() returned null");
48
+ }
49
+
50
+ auto imageView = imageViewBase.TryQueryInterface<IImageView3>();
51
+ if (!imageView) {
52
+ throw apmf::InvalidArgumentError("ExpoImage: IImageView3 query returned null");
53
+ }
54
+
55
+ clipView->addChild(imageViewBase);
56
+ containerPtr->addChild(clipView);
57
+
58
+ void* key = containerPtr.get();
59
+ views_[key] = std::make_unique<ExpoImageView>(viewModule_, containerPtr, clipView, imageView, store_);
60
+
61
+ // erase the per-view entry when the IView detaches.
62
+ //
63
+ // The log proved cells are genuinely RECREATED on every navigation step
64
+ // (createEntry fires per step; liveEntries climbs monotonically), so an
65
+ // earlier "never erase" experiment leaked an ExpoImageView (+ detached
66
+ // clip/image render objects) per step (102 -> 196+ and growing). Erasing is
67
+ // now both correct AND flicker-free, because the decoded image is persisted
68
+ // by the shared ExpoImageStore cache (not by the view) -- a recreated cell
69
+ // re-paints synchronously from the cache in loadSource(). An in-flight load
70
+ // also survives this erase: it runs on the shared loader and its listener
71
+ // owns the captures, so it still completes and warms the cache.
72
+ auto sub = containerPtr->addLifecycleListener(apmf::Fn(
73
+ [](apmf::method::onMount, View<IView>) {},
74
+ [this, key](apmf::method::onUnmount, View<IView>) {
75
+ auto it = views_.find(key);
76
+ if (it == views_.end()) return;
77
+ // Move the entry out first so ~ExpoImageView runs only AFTER the
78
+ // map is consistent and this callback has returned.
79
+ auto doomed = std::move(it->second);
80
+ views_.erase(it);
81
+ }));
82
+ views_[key]->lifecycleSub_ = std::move(sub);
83
+ return containerPtr;
84
+ }
85
+
86
+ Ptr<IView> ExpoImageComponentViewManager::makeView() {
87
+ return createEntry(viewModule_->makeView());
88
+ }
89
+
90
+ ExpoImageView& ExpoImageComponentViewManager::ensureEntry(View<IView> view) {
91
+ void* key = view.get();
92
+ auto it = views_.find(key);
93
+ if (it == views_.end()) {
94
+ createEntry(Ptr<IView>(view));
95
+ it = views_.find(key);
96
+ }
97
+ return *it->second;
98
+ }
99
+
100
+ Ptr<IPropsRegistry> ExpoImageComponentViewManager::registerProps(View<IPropsRegistryBuilder> builder) {
101
+ auto emptyObj = context_->makeJsObjectBuilder()->build();
102
+ builder->stringPropWithDefault("contentFit", "cover");
103
+ builder->stringPropWithDefault("placeholderContentFit", "scale-down");
104
+ builder->boolPropWithDefault("allowDownscaling", true);
105
+ builder->objectPropWithDefault("contentPosition", emptyObj);
106
+ builder->intPropWithDefault("tintColor", std::numeric_limits<int>::max());
107
+ builder->intPropWithDefault("overlayColor", std::numeric_limits<int>::max());
108
+ builder->doublePropWithDefault("blurRadius", 0.0);
109
+ builder->arrayProp("placeholder");
110
+ builder->arrayProp("source");
111
+ builder->stringPropWithDefault("priority", "normal");
112
+ builder->objectPropWithDefault("transition", emptyObj);
113
+ builder->stringPropWithDefault("recyclingKey", "");
114
+ builder->stringPropWithDefault("accessibilityLabel", "");
115
+ builder->stringPropWithDefault("alt", "");
116
+ builder->boolPropWithDefault("accessible", true);
117
+ return builder->build();
118
+ }
119
+
120
+ void ExpoImageComponentViewManager::updateContext(View<IContext> context) {
121
+ context_ = context;
122
+ }
123
+
124
+ void ExpoImageComponentViewManager::updateProps(View<IView> view, View<IJsObject> newProps) {
125
+ ensureEntry(view).updateProps(newProps, context_);
126
+ }
127
+
128
+ void ExpoImageComponentViewManager::updateLayout(View<IView> view, View<ILayout> newLayout) {
129
+ ensureEntry(view).updateLayout(newLayout);
130
+ }
131
+
132
+ } // namespace expoimage
@@ -0,0 +1,66 @@
1
+ #pragma once
2
+
3
+ #include <apmf/apmf_base.h>
4
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/react/IContext.h>
5
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/react/IContextDelegate.h>
6
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/react/IJsObject.h>
7
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/react/ILayout.h>
8
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/react/ILayoutDelegate.h>
9
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/react/IPropsDelegate.h>
10
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/react/IPropsRegistry.h>
11
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/react/IPropsRegistryBuilder.h>
12
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/react/IViewManager.h>
13
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/view/IView.h>
14
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/view/IViewModule.h>
15
+ #include <apmf/ptr.h>
16
+
17
+ #include "ExpoImageView.h"
18
+
19
+ #include <memory>
20
+ #include <unordered_map>
21
+
22
+ namespace expoimage {
23
+
24
+ using apmf::ApmfBase;
25
+ using apmf::Ptr;
26
+ using apmf::View;
27
+ using apmf::iface::com::amazon::kepler::uitoolkit::react::IContext;
28
+ using apmf::iface::com::amazon::kepler::uitoolkit::react::IContextDelegate;
29
+ using apmf::iface::com::amazon::kepler::uitoolkit::react::IJsObject;
30
+ using apmf::iface::com::amazon::kepler::uitoolkit::react::ILayout;
31
+ using apmf::iface::com::amazon::kepler::uitoolkit::react::ILayoutDelegate;
32
+ using apmf::iface::com::amazon::kepler::uitoolkit::react::IPropsDelegate;
33
+ using apmf::iface::com::amazon::kepler::uitoolkit::react::IPropsRegistry;
34
+ using apmf::iface::com::amazon::kepler::uitoolkit::react::IPropsRegistryBuilder;
35
+ using apmf::iface::com::amazon::kepler::uitoolkit::react::IViewManager;
36
+ using apmf::iface::com::amazon::kepler::uitoolkit::view::IView;
37
+ using apmf::iface::com::amazon::kepler::uitoolkit::view::IViewModule;
38
+
39
+ /**
40
+ * Singleton ViewManager — thin dispatcher that delegates to per-view ExpoImageView instances.
41
+ */
42
+ struct ExpoImageComponentViewManager
43
+ : ApmfBase<ExpoImageComponentViewManager, IViewManager, IContextDelegate, IPropsDelegate, ILayoutDelegate> {
44
+ public:
45
+ explicit ExpoImageComponentViewManager(View<IViewModule> viewModule);
46
+
47
+ Ptr<IView> makeView();
48
+ Ptr<IPropsRegistry> registerProps(View<IPropsRegistryBuilder> builder);
49
+ void updateContext(View<IContext> context);
50
+ void updateProps(View<IView> view, View<IJsObject> newProps);
51
+ void updateLayout(View<IView> view, View<ILayout> newLayout);
52
+
53
+ private:
54
+ ExpoImageView& ensureEntry(View<IView> view);
55
+ Ptr<IView> createEntry(Ptr<IView> containerPtr);
56
+
57
+ Ptr<IViewModule> viewModule_;
58
+ Ptr<IContext> context_;
59
+ std::unordered_map<void*, std::unique_ptr<ExpoImageView>> views_;
60
+ // shared loader + decoded-image cache, lives for the whole
61
+ // process (this view manager is a singleton). Shared by every ExpoImageView so
62
+ // loads survive cell recreation and repeat posters paint synchronously.
63
+ std::shared_ptr<ExpoImageStore> store_;
64
+ };
65
+
66
+ } // namespace expoimage
@@ -0,0 +1,102 @@
1
+ /*
2
+ * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ *
4
+ * PROPRIETARY/CONFIDENTIAL
5
+ * Use is subject to license terms.
6
+ */
7
+
8
+ #pragma once
9
+
10
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/view/IImage.h>
11
+ #include <apmf/iface/com/amazon/kepler/uitoolkit/view/IImageLoader.h>
12
+ #include <apmf/ptr.h>
13
+
14
+ #include <list>
15
+ #include <memory>
16
+ #include <string>
17
+ #include <unordered_map>
18
+
19
+ namespace expoimage {
20
+
21
+ using apmf::Ptr;
22
+ namespace vi = apmf::iface::com::amazon::kepler::uitoolkit::view;
23
+
24
+ /**
25
+ * process-wide store shared by ALL expo-image cells, owned as a
26
+ * single process-wide instance (sharedExpoImageStore()). Two responsibilities:
27
+ * - `loader`: a SINGLE IImageLoader shared by every cell. Because it outlives any
28
+ * individual cell, an in-flight load is NOT canceled when a fast-navigated
29
+ * recycling row tears the cell down -- the load completes and populates the
30
+ * cache for next time.
31
+ * - the cache: a bounded key -> decoded IImage map. A (re)created cell whose key
32
+ * is already cached paints SYNCHRONOUSLY (setImage same frame) with no async
33
+ * round-trip, mirroring the pre-migration cached behaviour.
34
+ *
35
+ * Threading: every member is touched only on the UI thread. The stable
36
+ * IImageLoader posts its onSuccess/onFailure callbacks back to the UI RunLoop
37
+ * (download/decode happen on worker threads, completion is dispatched to the UI
38
+ * thread), so get()/put()/clear() and the cell prop/layout updates all run on the
39
+ * same thread -- no synchronization is required. clearMemoryCache() schedules
40
+ * clear() onto the UI RunLoop for the same reason.
41
+ */
42
+ struct ExpoImageStore {
43
+ Ptr<vi::IImageLoader> loader;
44
+
45
+ // Sized to comfortably hold a full row's worth of posters (plus a margin) so a
46
+ // second pass through a list stays fully cached with no eviction re-flicker.
47
+ // Decoded images are view-sized (see computePreferredSize), so each entry is
48
+ // small.
49
+ size_t maxEntries{128};
50
+
51
+ // Returns the cached image for `key`, promoting it to most-recently-used.
52
+ Ptr<vi::IImage> get(const std::string& key) {
53
+ auto it = cache_.find(key);
54
+ if (it == cache_.end()) {
55
+ return Ptr<vi::IImage>{};
56
+ }
57
+ lru_.splice(lru_.end(), lru_, it->second.lruIt); // promote to MRU
58
+ return it->second.img;
59
+ }
60
+
61
+ // Inserts/updates `key`, evicting the least-recently-used entry at capacity.
62
+ void put(const std::string& key, Ptr<vi::IImage> img) {
63
+ if (key.empty() || !img) {
64
+ return;
65
+ }
66
+ auto it = cache_.find(key);
67
+ if (it != cache_.end()) {
68
+ it->second.img = img;
69
+ lru_.splice(lru_.end(), lru_, it->second.lruIt); // promote to MRU
70
+ return;
71
+ }
72
+ while (cache_.size() >= maxEntries && !lru_.empty()) {
73
+ cache_.erase(lru_.front());
74
+ lru_.pop_front();
75
+ }
76
+ lru_.push_back(key);
77
+ cache_.emplace(key, Entry{img, std::prev(lru_.end())});
78
+ }
79
+
80
+ // Drops all cached decoded images (memory cache). The shared loader is kept.
81
+ void clear() {
82
+ cache_.clear();
83
+ lru_.clear();
84
+ }
85
+
86
+ private:
87
+ struct Entry {
88
+ Ptr<vi::IImage> img;
89
+ std::list<std::string>::iterator lruIt;
90
+ };
91
+ std::list<std::string> lru_; // front = LRU, back = MRU
92
+ std::unordered_map<std::string, Entry> cache_;
93
+ };
94
+
95
+ // Single process-wide store, shared by every ExpoImageComponentViewManager and by
96
+ // the ExpoImage TurboModule (so JS clearMemoryCache() can release it).
97
+ inline std::shared_ptr<ExpoImageStore> sharedExpoImageStore() {
98
+ static std::shared_ptr<ExpoImageStore> instance = std::make_shared<ExpoImageStore>();
99
+ return instance;
100
+ }
101
+
102
+ } // namespace expoimage