Skip to content

Commit

Permalink
[Fabric][Image] Add tintColor, blurRadius, and resizeMode = repeat (#…
Browse files Browse the repository at this point in the history
…11064)

* Change files

* [Fabric][Image] Add tintColor, blurRadius, and resizeMode = repeat

* formatting

* fix blurRadius

* formatting
  • Loading branch information
marlenecota authored Jan 23, 2023
1 parent e43204c commit 3cd21c2
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "[Fabric][Image] Add tintColor, blurRadius, and resizeMode = repeat",
"packageName": "react-native-windows",
"email": "email not defined",
"dependentChangeType": "patch"
}
141 changes: 111 additions & 30 deletions vnext/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <Fabric/FabricUIManagerModule.h>
#include <Utils/ImageUtils.h>
#include <shcore.h>
#include <winrt/Windows.Graphics.Effects.h>
#include <winrt/Windows.UI.Composition.h>
#include <winrt/Windows.Web.Http.Headers.h>
#include <winrt/Windows.Web.Http.h>
Expand Down Expand Up @@ -83,7 +84,7 @@ void ImageComponentView::didReceiveImage(const winrt::com_ptr<IWICBitmap> &wicbm
imageEventEmitter->onLoadEnd();
}

// TODO - handle m_props.tintColor, imageProps.resizeMode, imageProps.capInsets, imageProps.blurRadius
// TODO - handle imageProps.capInsets

#ifdef DEBUG
auto uiDispatcher = m_context.UIDispatcher();
Expand Down Expand Up @@ -112,7 +113,9 @@ void ImageComponentView::updateProps(

updateBorderProps(oldImageProps, newImageProps);

if (oldImageProps.backgroundColor != newImageProps.backgroundColor) {
if (oldImageProps.backgroundColor != newImageProps.backgroundColor ||
oldImageProps.blurRadius != newImageProps.blurRadius || oldImageProps.tintColor != newImageProps.tintColor ||
oldImageProps.resizeMode != newImageProps.resizeMode) {
m_drawingSurface = nullptr; // TODO dont need to recreate the surface just to redraw...
}

Expand Down Expand Up @@ -203,17 +206,32 @@ void ImageComponentView::ensureDrawingSurface() noexcept {
winrt::check_hresult(m_wicbmp->GetSize(&width, &height));

if (!m_drawingSurface && 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);
const auto frame{m_layoutMetrics.getContentFrame().size};

if (imageProps->resizeMode == facebook::react::ImageResizeMode::Repeat) {
drawingSurfaceSize = {frame.width, frame.height};
} else if (imageProps->blurRadius > 0) {
// https://learn.microsoft.com/en-us/windows/win32/direct2d/gaussian-blur#output-bitmap
// The following equation that can be used to compute the output bitmap:
// Output bitmap growth (X and Y) = (StandardDeviation(DIPs)*3 + StandardDeviation(DIPs)*3)*((User DPI)/96)
// Where StandardDeviation(DIPs)*3 is equivalent to the blur radius.
const auto bmpGrowth{imageProps->blurRadius * 2 * m_layoutMetrics.pointScaleFactor};
drawingSurfaceSize = {drawingSurfaceSize.Width + bmpGrowth, drawingSurfaceSize.Height + bmpGrowth};
}

m_drawingSurface = m_compContext.CreateDrawingSurface(
{static_cast<float>(width), static_cast<float>(height)},
drawingSurfaceSize,
winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
winrt::Windows::Graphics::DirectX::DirectXAlphaMode::Premultiplied);

DrawImage();

auto surfaceBrush = m_compContext.CreateSurfaceBrush(m_drawingSurface);

const auto &imageProps = *std::static_pointer_cast<const facebook::react::ImageProps>(m_props);
switch (imageProps.resizeMode) {
switch (imageProps->resizeMode) {
case facebook::react::ImageResizeMode::Stretch:
surfaceBrush.Stretch(winrt::Microsoft::ReactNative::Composition::CompositionStretch::Fill);
break;
Expand All @@ -223,16 +241,24 @@ void ImageComponentView::ensureDrawingSurface() noexcept {
case facebook::react::ImageResizeMode::Contain:
surfaceBrush.Stretch(winrt::Microsoft::ReactNative::Composition::CompositionStretch::Uniform);
break;
case facebook::react::ImageResizeMode::Center:
surfaceBrush.Stretch(winrt::Microsoft::ReactNative::Composition::CompositionStretch::None);
break;
case facebook::react::ImageResizeMode::Repeat:
surfaceBrush.Stretch(winrt::Microsoft::ReactNative::Composition::CompositionStretch::UniformToFill);
// TODO - Hook up repeat
// TODO - set AlignmentRatio back to 0.5f when switching between resizeModes once we no longer recreate the
// drawing surface on prop changes.
surfaceBrush.HorizontalAlignmentRatio(0.0f);
surfaceBrush.VerticalAlignmentRatio(0.0f);
// Repeat and Center use the same Stretch logic, so we can fallthrough here.
[[fallthrough]];
case facebook::react::ImageResizeMode::Center: {
surfaceBrush.Stretch(
(height < frame.height && width < frame.width)
? winrt::Microsoft::ReactNative::Composition::CompositionStretch::None
: winrt::Microsoft::ReactNative::Composition::CompositionStretch::Uniform);
break;
}
default:
assert(false);
}

m_visual.Brush(surfaceBrush);
}
}
Expand All @@ -250,8 +276,6 @@ void ImageComponentView::DrawImage() noexcept {
m_drawingSurface.as(drawingSurfaceInterop);

if (CheckForDeviceRemoved(drawingSurfaceInterop->BeginDraw(d2dDeviceContext.put(), &offset))) {
const auto &paragraphProps = *std::static_pointer_cast<const facebook::react::ImageProps>(m_props);

winrt::com_ptr<ID2D1Bitmap1> bitmap;
winrt::check_hresult(d2dDeviceContext->CreateBitmapFromWicBitmap(m_wicbmp.get(), nullptr, bitmap.put()));

Expand All @@ -260,24 +284,81 @@ 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 + width) / m_layoutMetrics.pointScaleFactor),
static_cast<float>((offset.y + height) / m_layoutMetrics.pointScaleFactor));

const auto dpi = m_layoutMetrics.pointScaleFactor * 96.0f;
float oldDpiX, oldDpiY;
d2dDeviceContext->GetDpi(&oldDpiX, &oldDpiY);
d2dDeviceContext->SetDpi(dpi, dpi);

d2dDeviceContext->DrawBitmap(bitmap.get(), rect);

// Restore old dpi setting
d2dDeviceContext->SetDpi(oldDpiX, oldDpiY);
const auto imageProps = std::static_pointer_cast<const facebook::react::ImageProps>(m_props);

bool useEffects{
imageProps->blurRadius > 0 || isColorMeaningful(imageProps->tintColor) ||
imageProps->resizeMode == facebook::react::ImageResizeMode::Repeat};

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()));

if (imageProps->blurRadius > 0) {
winrt::com_ptr<ID2D1Effect> gaussianBlurEffect;
winrt::check_hresult(d2dDeviceContext->CreateEffect(CLSID_D2D1GaussianBlur, gaussianBlurEffect.put()));
// https://learn.microsoft.com/en-us/windows/win32/direct2d/gaussian-blur#effect-properties
// You can compute the blur radius of the kernel by multiplying the standard deviation by 3 (radius multiplier).
constexpr float radiusMultiplier = 3;
winrt::check_hresult(gaussianBlurEffect->SetValue(
D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, (imageProps->blurRadius) / radiusMultiplier));
gaussianBlurEffect->SetInputEffect(0, bitmapEffects.get());
bitmapEffects.copy_from(gaussianBlurEffect.get());
}

if (isColorMeaningful(imageProps->tintColor)) {
winrt::com_ptr<ID2D1Effect> tintColorEffect;
winrt::check_hresult(d2dDeviceContext->CreateEffect(CLSID_D2D1Flood, tintColorEffect.put()));
winrt::check_hresult(tintColorEffect->SetValue(D2D1_FLOOD_PROP_COLOR, imageProps->tintColor.AsD2DColor()));

winrt::com_ptr<ID2D1Effect> compositeEffect;
winrt::check_hresult(d2dDeviceContext->CreateEffect(CLSID_D2D1Composite, compositeEffect.put()));
winrt::check_hresult(compositeEffect->SetValue(D2D1_COMPOSITE_PROP_MODE, D2D1_COMPOSITE_MODE_SOURCE_IN));
compositeEffect->SetInputEffect(0, bitmapEffects.get());
compositeEffect->SetInputEffect(1, tintColorEffect.get());

bitmapEffects.copy_from(compositeEffect.get());
}

if (imageProps->resizeMode == facebook::react::ImageResizeMode::Repeat) {
winrt::com_ptr<ID2D1Effect> borderEffect;
winrt::check_hresult(d2dDeviceContext->CreateEffect(CLSID_D2D1Border, borderEffect.put()));
winrt::check_hresult(borderEffect->SetValue(D2D1_BORDER_PROP_EDGE_MODE_X, D2D1_BORDER_EDGE_MODE_WRAP));
winrt::check_hresult(borderEffect->SetValue(D2D1_BORDER_PROP_EDGE_MODE_Y, D2D1_BORDER_EDGE_MODE_WRAP));
borderEffect->SetInputEffect(0, bitmapEffects.get());

d2dDeviceContext->DrawImage(borderEffect.get());
} else {
winrt::com_ptr<ID2D1Image> image;
bitmapEffects->GetOutput(image.put());

D2D1_RECT_F imageBounds;
winrt::check_hresult(d2dDeviceContext->GetImageLocalBounds(image.get(), &imageBounds));

d2dDeviceContext->DrawImage(
bitmapEffects.get(), {static_cast<float>(offset.x), static_cast<float>(offset.y)}, imageBounds);
}
} else {
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 + width) / m_layoutMetrics.pointScaleFactor),
static_cast<float>((offset.y + height) / m_layoutMetrics.pointScaleFactor));

const auto dpi = m_layoutMetrics.pointScaleFactor * 96.0f;
float oldDpiX, oldDpiY;
d2dDeviceContext->GetDpi(&oldDpiX, &oldDpiY);
d2dDeviceContext->SetDpi(dpi, dpi);

d2dDeviceContext->DrawBitmap(bitmap.get(), rect);

// Restore old dpi setting
d2dDeviceContext->SetDpi(oldDpiX, oldDpiY);
}

// Our update is done. EndDraw never indicates rendering device removed, so any
// failure here is unexpected and, therefore, fatal.
Expand Down

0 comments on commit 3cd21c2

Please sign in to comment.