Skip to content

Commit

Permalink
Fabric: ImageRequest should kick off from ImageShadowNode instead of …
Browse files Browse the repository at this point in the history
…ImageComponentView (#10672)

* Fabric: fix zorder issues

* Change files

* Fabric: simplify border code, and fix some border issues

* change file

* rename const

* format

* Move image loading to ImageShadowNode

* format

* change file
  • Loading branch information
acoates-ms committed Oct 6, 2022
1 parent 39ae0be commit d6d0fa2
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 165 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Fabric: ImageRequest should kick off from ImageShadowNode instead of ImageComponentView",
"packageName": "react-native-windows",
"email": "30809111+acoates-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
228 changes: 92 additions & 136 deletions vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@

#include <IReactContext.h>

#pragma warning(push)
#pragma warning(disable : 4244 4305)
#include <react/renderer/components/image/ImageProps.h>
#pragma warning(pop)
#include <react/renderer/components/image/ImageEventEmitter.h>

#include <Fabric/FabricUIManagerModule.h>
#include <Utils/ImageUtils.h>
#include <shcore.h>
#include <winrt/Windows.UI.Composition.h>
Expand All @@ -28,24 +25,33 @@ extern "C" HRESULT WINAPI WICCreateImagingFactory_Proxy(UINT SDKVersion, IWICIma

namespace Microsoft::ReactNative {

ImageComponentView::WindowsImageResponseObserver::WindowsImageResponseObserver(
std::shared_ptr<ImageComponentView> image) {
m_image = image;
}

void ImageComponentView::WindowsImageResponseObserver::didReceiveProgress(float progress) const {
// TODO progress?
}

void ImageComponentView::WindowsImageResponseObserver::didReceiveImage(
facebook::react::ImageResponse const &imageResponse) const {
auto sharedwicbmp = std::static_pointer_cast<winrt::com_ptr<IWICBitmap>>(imageResponse.getImage());
m_image->m_context.UIDispatcher().Post(
[wicbmp = *sharedwicbmp, image = m_image]() { image->didReceiveImage(wicbmp); });
}

void ImageComponentView::WindowsImageResponseObserver::didReceiveFailure() const {
m_image->didReceiveFailureFromObserver();
}

ImageComponentView::ImageComponentView(
const winrt::Microsoft::ReactNative::Composition::ICompositionContext &compContext,
facebook::react::Tag tag,
winrt::Microsoft::ReactNative::ReactContext const &reactContext)
: Super(compContext, tag), m_context(reactContext) {
static auto const defaultProps = std::make_shared<facebook::react::ImageProps const>();
m_props = defaultProps;

/*
m_onLoadEndToken = m_element->OnLoadEnd([=](const auto &, const bool &succeeded) {
if (succeeded) {
std::static_pointer_cast<const facebook::react::ImageEventEmitter>(m_eventEmitter)->onLoad();
} else {
std::static_pointer_cast<const facebook::react::ImageEventEmitter>(m_eventEmitter)->onError();
}
std::static_pointer_cast<const facebook::react::ImageEventEmitter>(m_eventEmitter)->onLoadEnd();
});
*/
}

std::vector<facebook::react::ComponentDescriptorProvider>
Expand All @@ -61,89 +67,38 @@ void ImageComponentView::unmountChildComponentView(const IComponentView &childCo
assert(false);
}

void ImageComponentView::beginDownloadImage() noexcept {
ReactImageSource source;
source.uri = m_url;
source.height = m_imgHeight;
source.width = m_imgWidth;
source.sourceType = ImageSourceType::Download;
m_state = ImageState::Loading;
auto inputStreamTask = GetImageStreamAsync(source);
inputStreamTask.Completed([this](auto asyncOp, auto status) {
switch (status) {
case winrt::Windows::Foundation::AsyncStatus::Completed: {
if (m_state == ImageState::Loading) {
m_state = ImageState::Loaded;
generateBitmap(asyncOp.GetResults());
}
break;
}
case winrt::Windows::Foundation::AsyncStatus::Canceled: {
m_state = ImageState::Error;
break;
}
case winrt::Windows::Foundation::AsyncStatus::Error: {
m_state = ImageState::Error;
break;
}
case winrt::Windows::Foundation::AsyncStatus::Started: {
}
}
});
}

winrt::com_ptr<IWICBitmapSource> wicBitmapSourceFromStream(
const winrt::Windows::Storage::Streams::IRandomAccessStream &results) noexcept {
winrt::com_ptr<IWICBitmapDecoder> bitmapDecoder;
winrt::com_ptr<IWICImagingFactory> imagingFactory;
winrt::check_hresult(WICCreateImagingFactory_Proxy(WINCODEC_SDK_VERSION, imagingFactory.put()));

if (!results) {
return nullptr;
}

winrt::com_ptr<IStream> istream;
winrt::check_hresult(
CreateStreamOverRandomAccessStream(results.as<IUnknown>().get(), __uuidof(IStream), istream.put_void()));

if (imagingFactory->CreateDecoderFromStream(
istream.get(), nullptr, WICDecodeMetadataCacheOnDemand, bitmapDecoder.put()) < 0) {
return nullptr;
void ImageComponentView::ImageLoadStart() noexcept {
auto imageEventEmitter = std::static_pointer_cast<facebook::react::ImageEventEmitter const>(m_eventEmitter);
if (imageEventEmitter) {
imageEventEmitter->onLoadStart();
}

winrt::com_ptr<IWICBitmapFrameDecode> decodedFrame;
winrt::check_hresult(bitmapDecoder->GetFrame(0, decodedFrame.put()));
return decodedFrame;
}

void ImageComponentView::generateBitmap(const winrt::Windows::Storage::Streams::IRandomAccessStream &results) noexcept {
winrt::com_ptr<IWICBitmapSource> decodedFrame = wicBitmapSourceFromStream(results);
void ImageComponentView::didReceiveImage(const winrt::com_ptr<IWICBitmap> &wicbmp) noexcept {
// TODO check for recycled?

if (!decodedFrame) {
m_state = ImageState::Error;
return;
auto imageEventEmitter = std::static_pointer_cast<facebook::react::ImageEventEmitter const>(m_eventEmitter);
if (imageEventEmitter) {
imageEventEmitter->onLoad();
imageEventEmitter->onLoadEnd();
}

winrt::com_ptr<IWICImagingFactory> imagingFactory;
winrt::check_hresult(WICCreateImagingFactory_Proxy(WINCODEC_SDK_VERSION, imagingFactory.put()));
winrt::com_ptr<IWICFormatConverter> converter;
winrt::check_hresult(imagingFactory->CreateFormatConverter(converter.put()));
// TODO - handle m_props.tintColor, imageProps.resizeMode, imageProps.capInsets, imageProps.blurRadius

winrt::check_hresult(converter->Initialize(
decodedFrame.get(),
GUID_WICPixelFormat32bppPBGRA,
WICBitmapDitherTypeNone,
nullptr,
0.0f,
WICBitmapPaletteTypeMedianCut));
#ifdef DEBUG
auto uiDispatcher = m_context.UIDispatcher();
assert(uiDispatcher.HasThreadAccess());
#endif

winrt::check_hresult(imagingFactory->CreateBitmapFromSource(converter.get(), WICBitmapCacheOnLoad, m_wicbmp.put()));
m_wicbmp = wicbmp;
ensureDrawingSurface();
}

auto uiDispatcher = m_context.UIDispatcher();
if (uiDispatcher.HasThreadAccess()) {
ensureDrawingSurface();
} else {
uiDispatcher.Post([this]() { ensureDrawingSurface(); }); // TODO check capture ref
void ImageComponentView::didReceiveFailureFromObserver() noexcept {
auto imageEventEmitter = std::static_pointer_cast<facebook::react::ImageEventEmitter const>(m_eventEmitter);
if (imageEventEmitter) {
imageEventEmitter->onError();
imageEventEmitter->onLoadEnd();
}
}

Expand All @@ -165,38 +120,50 @@ void ImageComponentView::updateProps(
m_visual.Opacity(newImageProps.opacity);
}

if (oldImageProps.sources != newImageProps.sources) {
if (newImageProps.sources.empty()) {
// TODO clear image
} else {
m_reloadImage = true;
}
m_props = std::static_pointer_cast<facebook::react::ImageProps const>(props);
}

void ImageComponentView::updateState(
facebook::react::State::Shared const &state,
facebook::react::State::Shared const &oldState) noexcept {
auto oldImageState = std::static_pointer_cast<facebook::react::ImageShadowNode::ConcreteState const>(m_state);
auto newImageState = std::static_pointer_cast<facebook::react::ImageShadowNode::ConcreteState const>(state);

if (!m_imageResponseObserver) {
// Should ViewComponents enable_shared_from_this? then we dont need this dance to get a shared_ptr
std::shared_ptr<FabricUIManager> fabricuiManager =
::Microsoft::ReactNative::FabricUIManager::FromProperties(m_context.Properties());
auto componentViewDescriptor = fabricuiManager->GetViewRegistry().componentViewDescriptorWithTag(m_tag);

m_imageResponseObserver = std::make_shared<WindowsImageResponseObserver>(
std::static_pointer_cast<ImageComponentView>(componentViewDescriptor.view));
}

/*
if (oldImageProps.blurRadius != newImageProps.blurRadius) {
m_element->BlurRadius(newImageProps.blurRadius);
}
setStateAndResubscribeImageResponseObserver(newImageState);
bool havePreviousData = oldImageState && oldImageState->getData().getImageSource() != facebook::react::ImageSource{};

if (oldImageProps.tintColor != newImageProps.tintColor) {
if (newImageProps.tintColor) {
m_element->TintColor(newImageProps.tintColor.AsWindowsColor());
} else {
m_element->TintColor(winrt::Colors::Transparent());
}
}
if (!havePreviousData ||
(newImageState && newImageState->getData().getImageSource() != oldImageState->getData().getImageSource())) {
// Loading actually starts a little before this, but this is the first time we know
// the image is loading and can fire an event from this component
std::static_pointer_cast<facebook::react::ImageEventEmitter const>(m_eventEmitter)->onLoadStart();
}
}

if (oldImageProps.resizeMode != newImageProps.resizeMode) {
m_element->ResizeMode(newImageProps.resizeMode);
}
*/
void ImageComponentView::setStateAndResubscribeImageResponseObserver(
facebook::react::ImageShadowNode::ConcreteState::Shared const &state) noexcept {
if (m_state) {
auto &observerCoordinator = m_state->getData().getImageRequest().getObserverCoordinator();
observerCoordinator.removeObserver(*m_imageResponseObserver);
}

m_props = std::static_pointer_cast<facebook::react::ImageProps const>(props);
}
m_state = state;

void ImageComponentView::updateState(
facebook::react::State::Shared const &state,
facebook::react::State::Shared const &oldState) noexcept {}
if (m_state) {
auto &observerCoordinator = m_state->getData().getImageRequest().getObserverCoordinator();
observerCoordinator.addObserver(*m_imageResponseObserver);
}
}

void ImageComponentView::updateLayoutMetrics(
facebook::react::LayoutMetrics const &layoutMetrics,
Expand All @@ -221,24 +188,7 @@ void ImageComponentView::updateLayoutMetrics(
});
}

void ImageComponentView::finalizeUpdates(RNComponentViewUpdateMask updateMask) noexcept {
if (m_reloadImage) {
const auto &props = *std::static_pointer_cast<const facebook::react::ImageProps>(m_props);
m_url = props.sources[0].uri;
m_imgWidth = static_cast<unsigned int>(std::max(props.sources[0].size.width, m_layoutMetrics.frame.size.width));
m_imgHeight = static_cast<unsigned int>(std::max(props.sources[0].size.height, m_layoutMetrics.frame.size.height));
beginDownloadImage();
}

/*
if (m_needsOnLoadStart) {
std::static_pointer_cast<const facebook::react::ImageEventEmitter>(m_eventEmitter)->onLoadStart();
m_needsOnLoadStart = false;
}
*/

ensureDrawingSurface();
}
void ImageComponentView::finalizeUpdates(RNComponentViewUpdateMask updateMask) noexcept {}

void ImageComponentView::OnRenderingDeviceLost() noexcept {
// Draw the text again
Expand All @@ -248,9 +198,12 @@ void ImageComponentView::OnRenderingDeviceLost() noexcept {
void ImageComponentView::ensureDrawingSurface() noexcept {
assert(m_context.UIDispatcher().HasThreadAccess());

UINT width, height;
winrt::check_hresult(m_wicbmp->GetSize(&width, &height));

if (!m_drawingSurface && m_wicbmp) {
m_drawingSurface = m_compContext.CreateDrawingSurface(
{static_cast<float>(m_imgWidth), static_cast<float>(m_imgHeight)},
{static_cast<float>(width), static_cast<float>(height)},
winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
winrt::Windows::Graphics::DirectX::DirectXAlphaMode::Premultiplied);

Expand Down Expand Up @@ -306,11 +259,14 @@ void ImageComponentView::DrawImage() noexcept {
d2dDeviceContext->Clear(m_props->backgroundColor.AsD2DColor());
}

UINT width, height;
winrt::check_hresult(m_wicbmp->GetSize(&width, &height));

D2D1_RECT_F rect = D2D1::RectF(
static_cast<float>(offset.x / m_layoutMetrics.pointScaleFactor),
static_cast<float>(offset.y / m_layoutMetrics.pointScaleFactor),
static_cast<float>((offset.x + m_imgWidth) / m_layoutMetrics.pointScaleFactor),
static_cast<float>((offset.y + m_imgHeight) / m_layoutMetrics.pointScaleFactor));
static_cast<float>((offset.x + width) / m_layoutMetrics.pointScaleFactor),
static_cast<float>((offset.y + height) / m_layoutMetrics.pointScaleFactor));

const auto dpi = m_layoutMetrics.pointScaleFactor * 96.0f;
float oldDpiX, oldDpiY;
Expand Down
38 changes: 24 additions & 14 deletions vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#include <Microsoft.ReactNative.Cxx/ReactContext.h>
#include <Views/Image/ReactImage.h>
#include <react/renderer/components/image/ImageShadowNode.h>
#include <react/renderer/imagemanager/ImageResponseObserver.h>
#include <wincodec.h>
#include <winrt/Windows.UI.Composition.h>
#include "CompositionHelpers.h"
Expand All @@ -23,6 +25,8 @@

namespace Microsoft::ReactNative {

struct WindowsImageResponseObserver;

struct ImageComponentView : CompositionBaseComponentView {
using Super = CompositionBaseComponentView;
ImageComponentView(
Expand Down Expand Up @@ -51,30 +55,36 @@ struct ImageComponentView : CompositionBaseComponentView {
winrt::Microsoft::ReactNative::Composition::IVisual Visual() const noexcept override;

private:
struct WindowsImageResponseObserver : facebook::react::ImageResponseObserver {
public:
WindowsImageResponseObserver(std::shared_ptr<ImageComponentView> image);
void didReceiveProgress(float progress) const override;
void didReceiveImage(facebook::react::ImageResponse const &imageResponse) const override;
void didReceiveFailure() const override;

private:
std::shared_ptr<ImageComponentView> m_image;
};

void ensureVisual() noexcept;
void beginDownloadImage() noexcept;
void generateBitmap(const winrt::Windows::Storage::Streams::IRandomAccessStream &stream) noexcept;
void ensureDrawingSurface() noexcept;
void DrawImage() noexcept;

void ImageLoadStart() noexcept;
void ImageLoaded() noexcept;
void didReceiveImage(const winrt::com_ptr<IWICBitmap> &wicbmp) noexcept;
void didReceiveFailureFromObserver() noexcept;
void setStateAndResubscribeImageResponseObserver(
facebook::react::ImageShadowNode::ConcreteState::Shared const &state) noexcept;

facebook::react::SharedViewProps m_props;

winrt::Microsoft::ReactNative::Composition::SpriteVisual m_visual{nullptr};
winrt::Microsoft::ReactNative::ReactContext m_context;
winrt::Microsoft::ReactNative::Composition::ICompositionDrawingSurface m_drawingSurface;
winrt::com_ptr<IWICBitmap> m_wicbmp;
unsigned int m_imgWidth{0}, m_imgHeight{0};
bool m_reloadImage{false};

enum class ImageState {
None,
Loading,
Loaded,
Error,
};

ImageState m_state;
std::string m_url;
std::shared_ptr<WindowsImageResponseObserver> m_imageResponseObserver;
facebook::react::ImageShadowNode::ConcreteState::Shared m_state;
};

} // namespace Microsoft::ReactNative

0 comments on commit d6d0fa2

Please sign in to comment.