From cd5797996fd7dc37633a059d2fd730a335ab5465 Mon Sep 17 00:00:00 2001 From: Anupriya Verma <54227869+anupriya13@users.noreply.github.com> Date: Sat, 5 Apr 2025 11:45:52 +0530 Subject: [PATCH 1/4] Implement onProgress for Image --- packages/playground/Samples/image.tsx | 12 +++++- .../Fabric/Composition/ImageComponentView.cpp | 25 +++++++++++- .../Fabric/Composition/ImageComponentView.h | 1 + .../Fabric/WindowsImageManager.cpp | 39 +++++++++++++++---- .../Fabric/WindowsImageManager.h | 4 +- 5 files changed, 70 insertions(+), 11 deletions(-) diff --git a/packages/playground/Samples/image.tsx b/packages/playground/Samples/image.tsx index 2a613f71a2f..8cf7d11a17c 100644 --- a/packages/playground/Samples/image.tsx +++ b/packages/playground/Samples/image.tsx @@ -20,7 +20,9 @@ const loadingImageUri = const largeImageUri = 'https://cdn.freebiesupply.com/logos/large/2x/react-logo-png-transparent.png'; -const smallImageUri = +const smallImageUri = 'https://reactnative.dev/img/tiny_logo.png'; + +const flowerImageUri = 'https://cdn.pixabay.com/photo/2021/08/02/00/10/flowers-6515538_1280.jpg'; const reactLogoUri = 'https://reactjs.org/logo-og.png'; @@ -74,6 +76,8 @@ export default class Bootstrap extends React.Component< let imageUri = ''; if (value === 'small') { imageUri = smallImageUri; + } else if (value === 'flower') { + imageUri = flowerImageUri; } else if (value === 'large') { imageUri = largeImageUri; } else if (value === 'data-svg') { @@ -138,6 +142,10 @@ export default class Bootstrap extends React.Component< this.setSelection(value); }; + handleOnProgress = (event: any) => { + const {progress, loaded, total} = event.nativeEvent; + console.log(`Progress: ${progress}, Loaded = ${loaded} , Total = ${total}`); + }; render() { const resizeModes = [ {label: 'center', value: 'center'}, @@ -149,6 +157,7 @@ export default class Bootstrap extends React.Component< const imageSources = [ {label: 'small', value: 'small'}, + {label: 'flower', value: 'flower'}, {label: 'large', value: 'large'}, {label: 'data', value: 'data'}, {label: 'data-svg', value: 'data-svg'}, @@ -258,6 +267,7 @@ export default class Bootstrap extends React.Component< onLoad={() => console.log('onLoad')} onLoadStart={() => console.log('onLoadStart')} onLoadEnd={() => console.log('onLoadEnd')} + onProgress={this.handleOnProgress} /> )} diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.cpp index 715c930cd6b..d02a7317347 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.cpp @@ -36,7 +36,15 @@ ImageComponentView::WindowsImageResponseObserver::WindowsImageResponseObserver( void ImageComponentView::WindowsImageResponseObserver::didReceiveProgress(float progress, int64_t loaded, int64_t total) const { - // TODO progress? + if (auto imgComponentView = m_wkImage.get()) { + int loadedInt = static_cast(loaded); + int totalInt = static_cast(total); + imgComponentView->m_reactContext.UIDispatcher().Post([progress, wkImage = m_wkImage, loadedInt, totalInt]() { + if (auto image = wkImage.get()) { + image->didReceiveProgress(progress, loadedInt, totalInt); + } + }); + } } void ImageComponentView::WindowsImageResponseObserver::didReceiveImage( @@ -95,6 +103,21 @@ void ImageComponentView::ImageLoadStart() noexcept { } } +void ImageComponentView::ImageLoaded() noexcept { + auto imageEventEmitter = std::static_pointer_cast(m_eventEmitter); + if (imageEventEmitter) { + imageEventEmitter->onLoadEnd(); + } +} + +void ImageComponentView::didReceiveProgress(float progress, int loaded, int total) noexcept { + auto imageEventEmitter = std::static_pointer_cast(m_eventEmitter); + if (imageEventEmitter) { + imageEventEmitter->onProgress(progress, loaded, total); + } + ensureDrawingSurface(); +} + void ImageComponentView::didReceiveImage(const std::shared_ptr &imageResponseImage) noexcept { // TODO check for recycled? diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.h index cb450f5aa4e..0f97554312e 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.h @@ -85,6 +85,7 @@ struct ImageComponentView : ImageComponentViewT &wicbmp) noexcept; void didReceiveFailureFromObserver(const facebook::react::ImageLoadError &error) noexcept; void setStateAndResubscribeImageResponseObserver( diff --git a/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp b/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp index 9d9e26f7744..fc5620bded3 100644 --- a/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp @@ -78,7 +78,9 @@ wicBitmapSourceFromStream(const winrt::Windows::Storage::Streams::IRandomAccessS } winrt::Windows::Foundation::IAsyncOperation -WindowsImageManager::GetImageRandomAccessStreamAsync(ReactImageSource source) const { +WindowsImageManager::GetImageRandomAccessStreamAsync( + ReactImageSource source, + std::function progressCallback) const { co_await winrt::resume_background(); winrt::Windows::Foundation::Uri uri(winrt::to_hstring(source.uri)); @@ -130,8 +132,28 @@ WindowsImageManager::GetImageRandomAccessStreamAsync(ReactImageSource source) co response.ReasonPhrase(), response.StatusCode(), response.Headers()); } + auto inputStream = co_await response.Content().ReadAsInputStreamAsync(); + uint64_t total = response.Content().Headers().ContentLength().GetUInt64(); + uint64_t progress = 0; + winrt::Windows::Storage::Streams::InMemoryRandomAccessStream memoryStream; - co_await response.Content().WriteToStreamAsync(memoryStream); + winrt::Windows::Storage::Streams::DataReader reader(inputStream); + constexpr uint32_t bufferSize = 16 * 1024; + + while (true) { + uint32_t loaded = co_await reader.LoadAsync(bufferSize); + if (loaded == 0) + break; + + auto buffer = reader.ReadBuffer(loaded); + co_await memoryStream.WriteAsync(buffer); + progress += loaded; + + if (progressCallback) { + progressCallback(progress, total); + } + } + memoryStream.Seek(0); co_return winrt::Microsoft::ReactNative::Composition::StreamImageResponse(memoryStream.CloneStream()); @@ -160,7 +182,13 @@ facebook::react::ImageRequest WindowsImageManager::requestImage( source.width = imageSource.size.width; source.sourceType = ImageSourceType::Download; - imageResponseTask = GetImageRandomAccessStreamAsync(source); + auto progressCallback = [weakObserverCoordinator](int64_t progress, int64_t total) { + if (auto observerCoordinator = weakObserverCoordinator.lock()) { + float normalizedProgress = total > 0 ? static_cast(progress) / static_cast(total) : 1.0f; + observerCoordinator->nativeImageResponseProgress(normalizedProgress, progress, total); + } + }; + imageResponseTask = GetImageRandomAccessStreamAsync(source, progressCallback); } imageResponseTask.Completed([weakObserverCoordinator](auto asyncOp, auto status) { @@ -195,11 +223,6 @@ facebook::react::ImageRequest WindowsImageManager::requestImage( observerCoordinator->nativeImageResponseFailed(facebook::react::ImageLoadError(errorInfo)); break; } - case winrt::Windows::Foundation::AsyncStatus::Started: { - // TODO progress? - Can we register for progress off the download task? - // observerCoordinator->nativeImageResponseProgress(0.0/*progress*/, 0/*completed*/, 0/*total*/); - break; - } } }); return imageRequest; diff --git a/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.h b/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.h index 2d26bce8c65..0b315d44bb0 100644 --- a/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.h +++ b/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.h @@ -22,7 +22,9 @@ struct WindowsImageManager { private: winrt::Windows::Foundation::IAsyncOperation - GetImageRandomAccessStreamAsync(ReactImageSource source) const; + GetImageRandomAccessStreamAsync( + ReactImageSource source, + std::function progressCallback) const; winrt::Windows::Web::Http::HttpClient m_httpClient; winrt::Microsoft::ReactNative::ReactContext m_reactContext; From 5702f8b9502eaf5e7df662476c312594dd757d7e Mon Sep 17 00:00:00 2001 From: Anupriya Verma <54227869+anupriya13@users.noreply.github.com> Date: Sat, 5 Apr 2025 11:46:46 +0530 Subject: [PATCH 2/4] Change files --- ...ative-windows-eed51fa4-43c0-4164-8892-4c3fa0571940.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/react-native-windows-eed51fa4-43c0-4164-8892-4c3fa0571940.json diff --git a/change/react-native-windows-eed51fa4-43c0-4164-8892-4c3fa0571940.json b/change/react-native-windows-eed51fa4-43c0-4164-8892-4c3fa0571940.json new file mode 100644 index 00000000000..3ffd0ef3f96 --- /dev/null +++ b/change/react-native-windows-eed51fa4-43c0-4164-8892-4c3fa0571940.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Implement onProgress for Image", + "packageName": "react-native-windows", + "email": "54227869+anupriya13@users.noreply.github.com", + "dependentChangeType": "patch" +} From c1e23119f12e033ff55ff2f4a580bb20dd17959f Mon Sep 17 00:00:00 2001 From: Anupriya Verma <54227869+anupriya13@users.noreply.github.com> Date: Sat, 5 Apr 2025 14:37:21 +0530 Subject: [PATCH 3/4] Add safe check and rename variables --- .../Fabric/WindowsImageManager.cpp | 23 ++++++++++--------- .../Fabric/WindowsImageManager.h | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp b/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp index fc5620bded3..84a6ee64c77 100644 --- a/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp @@ -80,7 +80,7 @@ wicBitmapSourceFromStream(const winrt::Windows::Storage::Streams::IRandomAccessS winrt::Windows::Foundation::IAsyncOperation WindowsImageManager::GetImageRandomAccessStreamAsync( ReactImageSource source, - std::function progressCallback) const { + std::function progressCallback) const { co_await winrt::resume_background(); winrt::Windows::Foundation::Uri uri(winrt::to_hstring(source.uri)); @@ -133,24 +133,25 @@ WindowsImageManager::GetImageRandomAccessStreamAsync( } auto inputStream = co_await response.Content().ReadAsInputStreamAsync(); - uint64_t total = response.Content().Headers().ContentLength().GetUInt64(); - uint64_t progress = 0; + auto contentLengthRef = response.Content().Headers().ContentLength(); + uint64_t total = contentLengthRef ? contentLengthRef.GetUInt64() : 0; + uint64_t loaded = 0; winrt::Windows::Storage::Streams::InMemoryRandomAccessStream memoryStream; winrt::Windows::Storage::Streams::DataReader reader(inputStream); constexpr uint32_t bufferSize = 16 * 1024; while (true) { - uint32_t loaded = co_await reader.LoadAsync(bufferSize); - if (loaded == 0) + uint32_t loadedBuffer = co_await reader.LoadAsync(bufferSize); + if (loadedBuffer == 0) break; - auto buffer = reader.ReadBuffer(loaded); + auto buffer = reader.ReadBuffer(loadedBuffer); co_await memoryStream.WriteAsync(buffer); - progress += loaded; + loaded += loadedBuffer; if (progressCallback) { - progressCallback(progress, total); + progressCallback(loaded, total); } } @@ -182,10 +183,10 @@ facebook::react::ImageRequest WindowsImageManager::requestImage( source.width = imageSource.size.width; source.sourceType = ImageSourceType::Download; - auto progressCallback = [weakObserverCoordinator](int64_t progress, int64_t total) { + auto progressCallback = [weakObserverCoordinator](int64_t loaded, int64_t total) { if (auto observerCoordinator = weakObserverCoordinator.lock()) { - float normalizedProgress = total > 0 ? static_cast(progress) / static_cast(total) : 1.0f; - observerCoordinator->nativeImageResponseProgress(normalizedProgress, progress, total); + float progress = total > 0 ? static_cast(loaded) / static_cast(total) : 1.0f; + observerCoordinator->nativeImageResponseProgress(progress, loaded, total); } }; imageResponseTask = GetImageRandomAccessStreamAsync(source, progressCallback); diff --git a/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.h b/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.h index 0b315d44bb0..8d48afd38db 100644 --- a/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.h +++ b/vnext/Microsoft.ReactNative/Fabric/WindowsImageManager.h @@ -24,7 +24,7 @@ struct WindowsImageManager { winrt::Windows::Foundation::IAsyncOperation GetImageRandomAccessStreamAsync( ReactImageSource source, - std::function progressCallback) const; + std::function progressCallback) const; winrt::Windows::Web::Http::HttpClient m_httpClient; winrt::Microsoft::ReactNative::ReactContext m_reactContext; From d9e6a4959088368d156f65c223b30609c5f41355 Mon Sep 17 00:00:00 2001 From: Anupriya Verma <54227869+anupriya13@users.noreply.github.com> Date: Thu, 10 Apr 2025 08:07:00 +0530 Subject: [PATCH 4/4] Save the reactcontext from the ImageComponentView --- .../Fabric/Composition/ImageComponentView.cpp | 35 +++++++++---------- .../Fabric/Composition/ImageComponentView.h | 2 ++ 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.cpp index d02a7317347..782f17fcf62 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.cpp @@ -31,32 +31,29 @@ extern "C" HRESULT WINAPI WICCreateImagingFactory_Proxy(UINT SDKVersion, IWICIma namespace winrt::Microsoft::ReactNative::Composition::implementation { ImageComponentView::WindowsImageResponseObserver::WindowsImageResponseObserver( + winrt::Microsoft::ReactNative::ReactContext const &reactContext, winrt::weak_ref wkImage) - : m_wkImage(std::move(wkImage)) {} + : m_reactContext(reactContext), m_wkImage(std::move(wkImage)) {} void ImageComponentView::WindowsImageResponseObserver::didReceiveProgress(float progress, int64_t loaded, int64_t total) const { - if (auto imgComponentView = m_wkImage.get()) { - int loadedInt = static_cast(loaded); - int totalInt = static_cast(total); - imgComponentView->m_reactContext.UIDispatcher().Post([progress, wkImage = m_wkImage, loadedInt, totalInt]() { - if (auto image = wkImage.get()) { - image->didReceiveProgress(progress, loadedInt, totalInt); - } - }); - } + int loadedInt = static_cast(loaded); + int totalInt = static_cast(total); + m_reactContext.UIDispatcher().Post([progress, wkImage = m_wkImage, loadedInt, totalInt]() { + if (auto image = wkImage.get()) { + image->didReceiveProgress(progress, loadedInt, totalInt); + } + }); } void ImageComponentView::WindowsImageResponseObserver::didReceiveImage( facebook::react::ImageResponse const &imageResponse) const { - if (auto imgComponentView{m_wkImage.get()}) { - auto imageResponseImage = std::static_pointer_cast(imageResponse.getImage()); - imgComponentView->m_reactContext.UIDispatcher().Post([imageResponseImage, wkImage = m_wkImage]() { - if (auto image{wkImage.get()}) { - image->didReceiveImage(imageResponseImage); - } - }); - } + auto imageResponseImage = std::static_pointer_cast(imageResponse.getImage()); + m_reactContext.UIDispatcher().Post([imageResponseImage, wkImage = m_wkImage]() { + if (auto image{wkImage.get()}) { + image->didReceiveImage(imageResponseImage); + } + }); } void ImageComponentView::WindowsImageResponseObserver::didReceiveFailure( @@ -175,7 +172,7 @@ void ImageComponentView::updateState( auto newImageState = std::static_pointer_cast(state); if (!m_imageResponseObserver) { - m_imageResponseObserver = std::make_shared(get_weak()); + m_imageResponseObserver = std::make_shared(this->m_reactContext, get_weak()); } setStateAndResubscribeImageResponseObserver(newImageState); diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.h index 0f97554312e..e4c60e247a0 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.h @@ -71,6 +71,7 @@ struct ImageComponentView : ImageComponentViewT wkImage); void didReceiveProgress(float progress, int64_t loaded, int64_t total) const override; void didReceiveImage(const facebook::react::ImageResponse &imageResponse) const override; @@ -78,6 +79,7 @@ struct ImageComponentView : ImageComponentViewT m_wkImage; + winrt::Microsoft::ReactNative::ReactContext m_reactContext; }; void ensureDrawingSurface() noexcept;