Skip to content

Commit d449c00

Browse files
authored
[Fabric] Implement onProgress for Image (#14493)
1 parent 896616c commit d449c00

File tree

6 files changed

+87
-21
lines changed

6 files changed

+87
-21
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Implement onProgress for Image",
4+
"packageName": "react-native-windows",
5+
"email": "54227869+anupriya13@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

packages/playground/Samples/image.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ const loadingImageUri =
2020
const largeImageUri =
2121
'https://cdn.freebiesupply.com/logos/large/2x/react-logo-png-transparent.png';
2222

23-
const smallImageUri =
23+
const smallImageUri = 'https://reactnative.dev/img/tiny_logo.png';
24+
25+
const flowerImageUri =
2426
'https://cdn.pixabay.com/photo/2021/08/02/00/10/flowers-6515538_1280.jpg';
2527

2628
const reactLogoUri = 'https://reactjs.org/logo-og.png';
@@ -74,6 +76,8 @@ export default class Bootstrap extends React.Component<
7476
let imageUri = '';
7577
if (value === 'small') {
7678
imageUri = smallImageUri;
79+
} else if (value === 'flower') {
80+
imageUri = flowerImageUri;
7781
} else if (value === 'large') {
7882
imageUri = largeImageUri;
7983
} else if (value === 'data-svg') {
@@ -138,6 +142,10 @@ export default class Bootstrap extends React.Component<
138142
this.setSelection(value);
139143
};
140144

145+
handleOnProgress = (event: any) => {
146+
const {progress, loaded, total} = event.nativeEvent;
147+
console.log(`Progress: ${progress}, Loaded = ${loaded} , Total = ${total}`);
148+
};
141149
render() {
142150
const resizeModes = [
143151
{label: 'center', value: 'center'},
@@ -149,6 +157,7 @@ export default class Bootstrap extends React.Component<
149157

150158
const imageSources = [
151159
{label: 'small', value: 'small'},
160+
{label: 'flower', value: 'flower'},
152161
{label: 'large', value: 'large'},
153162
{label: 'data', value: 'data'},
154163
{label: 'data-svg', value: 'data-svg'},
@@ -258,6 +267,7 @@ export default class Bootstrap extends React.Component<
258267
onLoad={() => console.log('onLoad')}
259268
onLoadStart={() => console.log('onLoadStart')}
260269
onLoadEnd={() => console.log('onLoadEnd')}
270+
onProgress={this.handleOnProgress}
261271
/>
262272
)}
263273
</View>

vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.cpp

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,29 @@ extern "C" HRESULT WINAPI WICCreateImagingFactory_Proxy(UINT SDKVersion, IWICIma
3131
namespace winrt::Microsoft::ReactNative::Composition::implementation {
3232

3333
ImageComponentView::WindowsImageResponseObserver::WindowsImageResponseObserver(
34+
winrt::Microsoft::ReactNative::ReactContext const &reactContext,
3435
winrt::weak_ref<winrt::Microsoft::ReactNative::Composition::implementation::ImageComponentView> wkImage)
35-
: m_wkImage(std::move(wkImage)) {}
36+
: m_reactContext(reactContext), m_wkImage(std::move(wkImage)) {}
3637

3738
void ImageComponentView::WindowsImageResponseObserver::didReceiveProgress(float progress, int64_t loaded, int64_t total)
3839
const {
39-
// TODO progress?
40+
int loadedInt = static_cast<int>(loaded);
41+
int totalInt = static_cast<int>(total);
42+
m_reactContext.UIDispatcher().Post([progress, wkImage = m_wkImage, loadedInt, totalInt]() {
43+
if (auto image = wkImage.get()) {
44+
image->didReceiveProgress(progress, loadedInt, totalInt);
45+
}
46+
});
4047
}
4148

4249
void ImageComponentView::WindowsImageResponseObserver::didReceiveImage(
4350
facebook::react::ImageResponse const &imageResponse) const {
44-
if (auto imgComponentView{m_wkImage.get()}) {
45-
auto imageResponseImage = std::static_pointer_cast<ImageResponseImage>(imageResponse.getImage());
46-
imgComponentView->m_reactContext.UIDispatcher().Post([imageResponseImage, wkImage = m_wkImage]() {
47-
if (auto image{wkImage.get()}) {
48-
image->didReceiveImage(imageResponseImage);
49-
}
50-
});
51-
}
51+
auto imageResponseImage = std::static_pointer_cast<ImageResponseImage>(imageResponse.getImage());
52+
m_reactContext.UIDispatcher().Post([imageResponseImage, wkImage = m_wkImage]() {
53+
if (auto image{wkImage.get()}) {
54+
image->didReceiveImage(imageResponseImage);
55+
}
56+
});
5257
}
5358

5459
void ImageComponentView::WindowsImageResponseObserver::didReceiveFailure(
@@ -95,6 +100,21 @@ void ImageComponentView::ImageLoadStart() noexcept {
95100
}
96101
}
97102

103+
void ImageComponentView::ImageLoaded() noexcept {
104+
auto imageEventEmitter = std::static_pointer_cast<facebook::react::ImageEventEmitter const>(m_eventEmitter);
105+
if (imageEventEmitter) {
106+
imageEventEmitter->onLoadEnd();
107+
}
108+
}
109+
110+
void ImageComponentView::didReceiveProgress(float progress, int loaded, int total) noexcept {
111+
auto imageEventEmitter = std::static_pointer_cast<facebook::react::ImageEventEmitter const>(m_eventEmitter);
112+
if (imageEventEmitter) {
113+
imageEventEmitter->onProgress(progress, loaded, total);
114+
}
115+
ensureDrawingSurface();
116+
}
117+
98118
void ImageComponentView::didReceiveImage(const std::shared_ptr<ImageResponseImage> &imageResponseImage) noexcept {
99119
// TODO check for recycled?
100120

@@ -152,7 +172,7 @@ void ImageComponentView::updateState(
152172
auto newImageState = std::static_pointer_cast<facebook::react::ImageShadowNode::ConcreteState const>(state);
153173

154174
if (!m_imageResponseObserver) {
155-
m_imageResponseObserver = std::make_shared<WindowsImageResponseObserver>(get_weak());
175+
m_imageResponseObserver = std::make_shared<WindowsImageResponseObserver>(this->m_reactContext, get_weak());
156176
}
157177

158178
setStateAndResubscribeImageResponseObserver(newImageState);

vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,23 @@ struct ImageComponentView : ImageComponentViewT<ImageComponentView, ViewComponen
7171
struct WindowsImageResponseObserver : facebook::react::ImageResponseObserver {
7272
public:
7373
WindowsImageResponseObserver(
74+
winrt::Microsoft::ReactNative::ReactContext const &reactContext,
7475
winrt::weak_ref<winrt::Microsoft::ReactNative::Composition::implementation::ImageComponentView> wkImage);
7576
void didReceiveProgress(float progress, int64_t loaded, int64_t total) const override;
7677
void didReceiveImage(const facebook::react::ImageResponse &imageResponse) const override;
7778
void didReceiveFailure(const facebook::react::ImageLoadError &error) const override;
7879

7980
private:
8081
winrt::weak_ref<winrt::Microsoft::ReactNative::Composition::implementation::ImageComponentView> m_wkImage;
82+
winrt::Microsoft::ReactNative::ReactContext m_reactContext;
8183
};
8284

8385
void ensureDrawingSurface() noexcept;
8486
void DrawImage() noexcept;
8587

8688
void ImageLoadStart() noexcept;
8789
void ImageLoaded() noexcept;
90+
void didReceiveProgress(float progress, int loaded, int total) noexcept;
8891
void didReceiveImage(const std::shared_ptr<ImageResponseImage> &wicbmp) noexcept;
8992
void didReceiveFailureFromObserver(const facebook::react::ImageLoadError &error) noexcept;
9093
void setStateAndResubscribeImageResponseObserver(

vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ wicBitmapSourceFromStream(const winrt::Windows::Storage::Streams::IRandomAccessS
7878
}
7979

8080
winrt::Windows::Foundation::IAsyncOperation<winrt::Microsoft::ReactNative::Composition::ImageResponse>
81-
WindowsImageManager::GetImageRandomAccessStreamAsync(ReactImageSource source) const {
81+
WindowsImageManager::GetImageRandomAccessStreamAsync(
82+
ReactImageSource source,
83+
std::function<void(uint64_t loaded, uint64_t total)> progressCallback) const {
8284
co_await winrt::resume_background();
8385

8486
winrt::Windows::Foundation::Uri uri(winrt::to_hstring(source.uri));
@@ -130,8 +132,29 @@ WindowsImageManager::GetImageRandomAccessStreamAsync(ReactImageSource source) co
130132
response.ReasonPhrase(), response.StatusCode(), response.Headers());
131133
}
132134

135+
auto inputStream = co_await response.Content().ReadAsInputStreamAsync();
136+
auto contentLengthRef = response.Content().Headers().ContentLength();
137+
uint64_t total = contentLengthRef ? contentLengthRef.GetUInt64() : 0;
138+
uint64_t loaded = 0;
139+
133140
winrt::Windows::Storage::Streams::InMemoryRandomAccessStream memoryStream;
134-
co_await response.Content().WriteToStreamAsync(memoryStream);
141+
winrt::Windows::Storage::Streams::DataReader reader(inputStream);
142+
constexpr uint32_t bufferSize = 16 * 1024;
143+
144+
while (true) {
145+
uint32_t loadedBuffer = co_await reader.LoadAsync(bufferSize);
146+
if (loadedBuffer == 0)
147+
break;
148+
149+
auto buffer = reader.ReadBuffer(loadedBuffer);
150+
co_await memoryStream.WriteAsync(buffer);
151+
loaded += loadedBuffer;
152+
153+
if (progressCallback) {
154+
progressCallback(loaded, total);
155+
}
156+
}
157+
135158
memoryStream.Seek(0);
136159

137160
co_return winrt::Microsoft::ReactNative::Composition::StreamImageResponse(memoryStream.CloneStream());
@@ -160,7 +183,13 @@ facebook::react::ImageRequest WindowsImageManager::requestImage(
160183
source.width = imageSource.size.width;
161184
source.sourceType = ImageSourceType::Download;
162185

163-
imageResponseTask = GetImageRandomAccessStreamAsync(source);
186+
auto progressCallback = [weakObserverCoordinator](int64_t loaded, int64_t total) {
187+
if (auto observerCoordinator = weakObserverCoordinator.lock()) {
188+
float progress = total > 0 ? static_cast<float>(loaded) / static_cast<float>(total) : 1.0f;
189+
observerCoordinator->nativeImageResponseProgress(progress, loaded, total);
190+
}
191+
};
192+
imageResponseTask = GetImageRandomAccessStreamAsync(source, progressCallback);
164193
}
165194

166195
imageResponseTask.Completed([weakObserverCoordinator](auto asyncOp, auto status) {
@@ -195,11 +224,6 @@ facebook::react::ImageRequest WindowsImageManager::requestImage(
195224
observerCoordinator->nativeImageResponseFailed(facebook::react::ImageLoadError(errorInfo));
196225
break;
197226
}
198-
case winrt::Windows::Foundation::AsyncStatus::Started: {
199-
// TODO progress? - Can we register for progress off the download task?
200-
// observerCoordinator->nativeImageResponseProgress(0.0/*progress*/, 0/*completed*/, 0/*total*/);
201-
break;
202-
}
203227
}
204228
});
205229
return imageRequest;

vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ struct WindowsImageManager {
2929

3030
private:
3131
winrt::Windows::Foundation::IAsyncOperation<winrt::Microsoft::ReactNative::Composition::ImageResponse>
32-
GetImageRandomAccessStreamAsync(ReactImageSource source) const;
32+
GetImageRandomAccessStreamAsync(
33+
ReactImageSource source,
34+
std::function<void(uint64_t loaded, uint64_t total)> progressCallback) const;
3335

3436
winrt::Windows::Web::Http::HttpClient m_httpClient;
3537
winrt::Microsoft::ReactNative::ReactContext m_reactContext;

0 commit comments

Comments
 (0)