Skip to content

Commit

Permalink
[0.74] [Fabric] Add ability to provide custom image uri handlers (#13186
Browse files Browse the repository at this point in the history
)

* [Fabric] Add ability to provide custom image uri handlers (#13180)

* Add ability to provide custom image uri handlers

* Change files

* format

* fix

* fix

* fix change file
  • Loading branch information
acoates-ms committed May 4, 2024
1 parent 64ae33c commit b422f06
Show file tree
Hide file tree
Showing 15 changed files with 464 additions and 53 deletions.
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "Add ability to provide custom image uri handlers",
"packageName": "react-native-windows",
"email": "30809111+acoates-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
Expand Up @@ -23,6 +23,7 @@

#include <DesktopWindowBridge.h>
#include "App.xaml.h"
#include "AutoDraw.h"
#include "NativeModules.h"
#include "ReactPropertyBag.h"

Expand All @@ -41,6 +42,61 @@ winrt::Microsoft::UI::Composition::Compositor g_liftedCompositor{nullptr};

void RegisterCustomComponent(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) noexcept;

/**
* This ImageHandler will accept images with a uri using the ellipse protocol and render an ellipse image
*
* <Image
* style={{width: 400, height: 200}}
* source={{uri: 'customimage://test'}}
* />
*
* This allows applications to provide custom image rendering pipelines.
*/
struct EllipseImageHandler : winrt::implements<
EllipseImageHandler,
winrt::Microsoft::ReactNative::Composition::Experimental::IUriBrushProvider,
winrt::Microsoft::ReactNative::Composition::IUriImageProvider> {
bool CanLoadImageUri(winrt::Microsoft::ReactNative::IReactContext context, winrt::Windows::Foundation::Uri uri) {
return uri.SchemeName() == L"ellipse";
}

winrt::Windows::Foundation::IAsyncOperation<winrt::Microsoft::ReactNative::Composition::Experimental::UriBrushFactory>
GetSourceAsync(
const winrt::Microsoft::ReactNative::IReactContext &context,
const winrt::Microsoft::ReactNative::Composition::ImageSource &imageSource) {
co_return [uri = imageSource.Uri(), size = imageSource.Size(), scale = imageSource.Scale(), context](
const winrt::Microsoft::ReactNative::IReactContext &reactContext,
const winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext
&compositionContext) -> winrt::Microsoft::ReactNative::Composition::Experimental::IBrush {
auto compositor =
winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerCompositor(
compositionContext);
auto drawingBrush = compositionContext.CreateDrawingSurfaceBrush(
size,
winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
winrt::Windows::Graphics::DirectX::DirectXAlphaMode::Premultiplied);
POINT pt;
Microsoft::ReactNative::Composition::AutoDrawDrawingSurface autoDraw(drawingBrush, scale, &pt);
auto renderTarget = autoDraw.GetRenderTarget();

winrt::com_ptr<ID2D1SolidColorBrush> brush;
renderTarget->CreateSolidColorBrush({1.0f, 0.0f, 0.0f, 1.0f}, brush.put());
renderTarget->DrawEllipse(
{{(pt.x + size.Width / 2) / scale, (pt.y + size.Height / 2) / scale},
(size.Width / 2) / scale,
(size.Height / 2) / scale},
brush.get());

return drawingBrush;
};
}
};

void RegisterEllipseUriImageHandler(const winrt::Microsoft::ReactNative::ReactInstanceSettings &settings) noexcept {
winrt::Microsoft::ReactNative::Composition::UriImageManager::AddUriImageProvider(
settings.Properties(), winrt::make<EllipseImageHandler>());
}

// Have to use TurboModules to override built in modules.. so the standard attributed package provider doesn't work.
struct CompReactPackageProvider
: winrt::implements<CompReactPackageProvider, winrt::Microsoft::ReactNative::IReactPackageProvider> {
Expand Down Expand Up @@ -181,6 +237,9 @@ struct WindowData {
winrt::Microsoft::ReactNative::Composition::CompositionUIService::SetCompositor(
InstanceSettings(), g_liftedCompositor);

// Register ellipse:// uri hander for images
RegisterEllipseUriImageHandler(host.InstanceSettings());

auto bridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create(
g_liftedCompositor, winrt::Microsoft::UI::GetWindowIdFromWindow(hwnd));

Expand Down
Expand Up @@ -1966,6 +1966,12 @@ winrt::Microsoft::UI::Composition::ICompositionSurface MicrosoftCompositionConte
surface.try_as(s);
return s ? s->Inner() : nullptr;
}

winrt::Microsoft::ReactNative::Composition::Experimental::IBrush MicrosoftCompositionContextHelper::WrapBrush(
const winrt::Microsoft::UI::Composition::CompositionBrush &brush) noexcept {
return winrt::make<::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompBrush>(brush);
}

#endif

} // namespace winrt::Microsoft::ReactNative::Composition::Experimental::implementation
Expand Up @@ -45,6 +45,8 @@ struct MicrosoftCompositionContextHelper : MicrosoftCompositionContextHelperT<Mi
static winrt::Microsoft::UI::Composition::DropShadow InnerDropShadow(IDropShadow shadow) noexcept;
static winrt::Microsoft::UI::Composition::CompositionBrush InnerBrush(IBrush brush) noexcept;
static winrt::Microsoft::UI::Composition::ICompositionSurface InnerSurface(IDrawingSurfaceBrush surface) noexcept;

static IBrush WrapBrush(const winrt::Microsoft::UI::Composition::CompositionBrush &brush) noexcept;
};
#endif

Expand Down
Expand Up @@ -39,9 +39,9 @@ void ImageComponentView::WindowsImageResponseObserver::didReceiveProgress(float

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

void ImageComponentView::WindowsImageResponseObserver::didReceiveFailure() const {
Expand Down Expand Up @@ -83,7 +83,7 @@ void ImageComponentView::ImageLoadStart() noexcept {
}
}

void ImageComponentView::didReceiveImage(const winrt::com_ptr<IWICBitmap> &wicbmp) noexcept {
void ImageComponentView::didReceiveImage(const std::shared_ptr<ImageResponseImage> &imageResponseImage) noexcept {
// TODO check for recycled?

auto imageEventEmitter = std::static_pointer_cast<facebook::react::ImageEventEmitter const>(m_eventEmitter);
Expand All @@ -99,7 +99,7 @@ void ImageComponentView::didReceiveImage(const winrt::com_ptr<IWICBitmap> &wicbm
assert(uiDispatcher.HasThreadAccess());
#endif

m_wicbmp = wicbmp;
m_imageResponseImage = imageResponseImage;
ensureDrawingSurface();
}

Expand Down Expand Up @@ -198,7 +198,8 @@ void ImageComponentView::updateLayoutMetrics(
}

void ImageComponentView::OnRenderingDeviceLost() noexcept {
DrawImage();
m_drawingSurface = nullptr;
ensureDrawingSurface();
}

bool ImageComponentView::themeEffectsImage() const noexcept {
Expand All @@ -207,18 +208,26 @@ bool ImageComponentView::themeEffectsImage() const noexcept {

void ImageComponentView::onThemeChanged() noexcept {
if (themeEffectsImage()) {
DrawImage();
m_drawingSurface = nullptr;
ensureDrawingSurface();
}
Super::onThemeChanged();
}

void ImageComponentView::ensureDrawingSurface() noexcept {
assert(m_reactContext.UIDispatcher().HasThreadAccess());

UINT width, height;
winrt::check_hresult(m_wicbmp->GetSize(&width, &height));
if (!m_imageResponseImage) {
m_visual.Brush(nullptr);
return;
}

UINT width = 0, height = 0;
if (m_imageResponseImage->m_wicbmp) {
winrt::check_hresult(m_imageResponseImage->m_wicbmp->GetSize(&width, &height));
}

if (!m_drawingSurface && m_wicbmp) {
if (!m_drawingSurface && m_imageResponseImage->m_wicbmp) {
winrt::Windows::Foundation::Size drawingSurfaceSize{static_cast<float>(width), static_cast<float>(height)};

const auto imageProps = std::static_pointer_cast<const facebook::react::ImageProps>(m_props);
Expand Down Expand Up @@ -273,6 +282,8 @@ void ImageComponentView::ensureDrawingSurface() noexcept {
}

m_visual.Brush(m_drawingSurface);
} else if (m_imageResponseImage->m_brushFactory) {
m_visual.Brush(m_imageResponseImage->m_brushFactory(m_reactContext.Handle(), m_compContext));
}
}

Expand All @@ -286,7 +297,7 @@ void ImageComponentView::DrawImage() noexcept {
return;
}

if (!m_wicbmp) {
if (!m_imageResponseImage->m_wicbmp) {
return;
}

Expand All @@ -295,7 +306,8 @@ void ImageComponentView::DrawImage() noexcept {
::Microsoft::ReactNative::Composition::AutoDrawDrawingSurface autoDraw(m_drawingSurface, 1.0f, &offset);
if (auto d2dDeviceContext = autoDraw.GetRenderTarget()) {
winrt::com_ptr<ID2D1Bitmap1> bitmap;
winrt::check_hresult(d2dDeviceContext->CreateBitmapFromWicBitmap(m_wicbmp.get(), nullptr, bitmap.put()));
winrt::check_hresult(
d2dDeviceContext->CreateBitmapFromWicBitmap(m_imageResponseImage->m_wicbmp.get(), nullptr, bitmap.put()));

d2dDeviceContext->Clear(D2D1::ColorF(D2D1::ColorF::Black, 0.0f));
if (m_props->backgroundColor) {
Expand All @@ -311,7 +323,8 @@ void ImageComponentView::DrawImage() noexcept {
if (useEffects) {
winrt::com_ptr<ID2D1Effect> bitmapEffects;
winrt::check_hresult(d2dDeviceContext->CreateEffect(CLSID_D2D1BitmapSource, bitmapEffects.put()));
winrt::check_hresult(bitmapEffects->SetValue(D2D1_BITMAPSOURCE_PROP_WIC_BITMAP_SOURCE, m_wicbmp.get()));
winrt::check_hresult(
bitmapEffects->SetValue(D2D1_BITMAPSOURCE_PROP_WIC_BITMAP_SOURCE, m_imageResponseImage->m_wicbmp.get()));

if (imageProps->blurRadius > 0) {
winrt::com_ptr<ID2D1Effect> gaussianBlurEffect;
Expand Down Expand Up @@ -360,7 +373,7 @@ void ImageComponentView::DrawImage() noexcept {
}
} else {
UINT width, height;
winrt::check_hresult(m_wicbmp->GetSize(&width, &height));
winrt::check_hresult(m_imageResponseImage->m_wicbmp->GetSize(&width, &height));

D2D1_RECT_F rect = D2D1::RectF(
static_cast<float>(offset.x),
Expand Down
Expand Up @@ -14,6 +14,7 @@
#include <winrt/Windows.UI.Composition.h>
#include "CompositionHelpers.h"
#include "CompositionViewComponentView.h"
#include "ImageResponseImage.h"

#pragma warning(push)
#pragma warning(disable : 4244 4305)
Expand Down Expand Up @@ -83,7 +84,7 @@ struct ImageComponentView : ImageComponentViewT<ImageComponentView, ComponentVie

void ImageLoadStart() noexcept;
void ImageLoaded() noexcept;
void didReceiveImage(const winrt::com_ptr<IWICBitmap> &wicbmp) noexcept;
void didReceiveImage(const std::shared_ptr<ImageResponseImage> &wicbmp) noexcept;
void didReceiveFailureFromObserver() noexcept;
void setStateAndResubscribeImageResponseObserver(
facebook::react::ImageShadowNode::ConcreteState::Shared const &state) noexcept;
Expand All @@ -93,7 +94,7 @@ struct ImageComponentView : ImageComponentViewT<ImageComponentView, ComponentVie

winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual m_visual{nullptr};
winrt::Microsoft::ReactNative::Composition::Experimental::IDrawingSurfaceBrush m_drawingSurface;
winrt::com_ptr<IWICBitmap> m_wicbmp;
std::shared_ptr<ImageResponseImage> m_imageResponseImage;
std::shared_ptr<WindowsImageResponseObserver> m_imageResponseObserver;
facebook::react::ImageShadowNode::ConcreteState::Shared m_state;
};
Expand Down
@@ -0,0 +1,17 @@

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma once

#include <wincodec.h>
#include <winrt/Microsoft.ReactNative.Composition.Experimental.h>

namespace winrt::Microsoft::ReactNative::Composition::implementation {

struct ImageResponseImage {
winrt::com_ptr<IWICBitmap> m_wicbmp;
winrt::Microsoft::ReactNative::Composition::Experimental::UriBrushFactory m_brushFactory{nullptr};
};

} // namespace winrt::Microsoft::ReactNative::Composition::implementation
82 changes: 82 additions & 0 deletions vnext/Microsoft.ReactNative/Fabric/Composition/UriImageManager.cpp
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#include "pch.h"
#include "UriImageManager.h"

#include "Composition.ImageSource.g.h"
#include "Composition.UriImageManager.g.cpp"
#include <ReactPropertyBag.h>

namespace winrt::Microsoft::ReactNative::Composition::implementation {

struct ImageSource : ImageSourceT<ImageSource> {
ImageSource(const facebook::react::ImageSource &source)
: m_size({source.size.width, source.size.height}),
m_scale(source.scale),
m_uri(::Microsoft::ReactNative::UriTryCreate(winrt::to_hstring(source.uri))) {}

winrt::Windows::Foundation::Uri Uri() noexcept {
return m_uri;
}

winrt::Windows::Foundation::Size Size() noexcept {
return m_size;
}

float Scale() noexcept {
return m_scale;
}

private:
const winrt::Windows::Foundation::Uri m_uri;
const winrt::Windows::Foundation::Size m_size;
const float m_scale;
};

winrt::Microsoft::ReactNative::Composition::ImageSource MakeImageSource(
const facebook::react::ImageSource &source) noexcept {
return winrt::make<ImageSource>(source);
}

static const ReactPropertyId<ReactNonAbiValue<winrt::com_ptr<UriImageManager>>> &UriImageManagerPropertyId() noexcept {
static const ReactPropertyId<ReactNonAbiValue<winrt::com_ptr<UriImageManager>>> prop{
L"ReactNative", L"UriImageManager"};
return prop;
}

winrt::com_ptr<UriImageManager> UriImageManager::GetOrCreate(
const winrt::Microsoft::ReactNative::ReactPropertyBag &properties) noexcept {
auto uriImageManager =
winrt::Microsoft::ReactNative::ReactPropertyBag(properties).GetOrCreate(UriImageManagerPropertyId(), []() {
return winrt::make_self<UriImageManager>();
});
return uriImageManager.Value();
}

void UriImageManager::AddUriImageProvider(
const winrt::Microsoft::ReactNative::IReactPropertyBag &properties,
const IUriImageProvider &provider) {
if (!provider)
winrt::throw_hresult(E_INVALIDARG);
GetOrCreate(winrt::Microsoft::ReactNative::ReactPropertyBag(properties))->m_providers.push_back(provider);
}

IUriImageProvider UriImageManager::TryGetUriImageProvider(
const IReactContext &context,
winrt::Microsoft::ReactNative::Composition::ImageSource &source) noexcept {
auto uri = source.Uri();
if (!uri) {
return nullptr;
}

for (auto &provider : m_providers) {
if (provider.CanLoadImageUri(context, source.Uri())) {
return provider;
}
}

return nullptr;
}

} // namespace winrt::Microsoft::ReactNative::Composition::implementation
39 changes: 39 additions & 0 deletions vnext/Microsoft.ReactNative/Fabric/Composition/UriImageManager.h
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#pragma once
#include "Composition.UriImageManager.g.h"

#include <ReactPropertyBag.h>
#include <Utils/ImageUtils.h>
#include <react/renderer/imagemanager/primitives.h>
#include <winrt/Microsoft.ReactNative.Composition.h>

namespace winrt::Microsoft::ReactNative::Composition::implementation {

struct UriImageManager : UriImageManagerT<UriImageManager> {
UriImageManager() = default;

static void AddUriImageProvider(
const winrt::Microsoft::ReactNative::IReactPropertyBag &properties,
const IUriImageProvider &provider);

static winrt::com_ptr<UriImageManager> GetOrCreate(
const winrt::Microsoft::ReactNative::ReactPropertyBag &properties) noexcept;

IUriImageProvider TryGetUriImageProvider(
const IReactContext &context,
winrt::Microsoft::ReactNative::Composition::ImageSource &source) noexcept;

private:
std::vector<IUriImageProvider> m_providers;
};

winrt::Microsoft::ReactNative::Composition::ImageSource MakeImageSource(
const facebook::react::ImageSource &source) noexcept;

} // namespace winrt::Microsoft::ReactNative::Composition::implementation

namespace winrt::Microsoft::ReactNative::Composition::factory_implementation {
struct UriImageManager : UriImageManagerT<UriImageManager, implementation::UriImageManager> {};
} // namespace winrt::Microsoft::ReactNative::Composition::factory_implementation

0 comments on commit b422f06

Please sign in to comment.