Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "prerelease",
"comment": "Conditionally use BitmapImage",
"packageName": "react-native-windows",
"email": "email not defined",
"commit": "023ef6ee7d849d6fa1fc319040ead78d11328bf3",
"date": "2019-12-02T21:46:48.495Z"
}
167 changes: 135 additions & 32 deletions vnext/ReactUWP/Views/Image/ReactImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ namespace winrt {
using namespace Windows::Foundation;
using namespace Windows::Storage::Streams;
using namespace Windows::UI;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Media::Imaging;
using namespace Windows::Web::Http;
} // namespace winrt

Expand All @@ -28,18 +30,17 @@ using Microsoft::Common::Unicode::Utf8ToUtf16;
namespace react {
namespace uwp {

ReactImage::ReactImage() {
m_brush = ReactImageBrush::Create();
this->Background(m_brush.as<winrt::XamlCompositionBrushBase>());
}

/*static*/ winrt::com_ptr<ReactImage> ReactImage::Create() {
return winrt::make_self<ReactImage>();
}

winrt::Size ReactImage::ArrangeOverride(winrt::Size finalSize) {
auto brush{Background().as<ReactImageBrush>()};
brush->AvailableSize(finalSize);
m_availableSize = finalSize;

if (m_useCompositionBrush) {
auto brush{Background().as<ReactImageBrush>()};
brush->AvailableSize(finalSize);
}

return finalSize;
}
Expand All @@ -52,6 +53,48 @@ void ReactImage::OnLoadEnd(winrt::event_token const &token) noexcept {
m_onLoadEndEvent.remove(token);
}

void ReactImage::ResizeMode(react::uwp::ResizeMode value) {
if (m_resizeMode != value) {
m_resizeMode = value;

bool switchBrushes{false};

if (m_useCompositionBrush != ShouldUseCompositionBrush()) {
switchBrushes = true;
m_useCompositionBrush = ShouldUseCompositionBrush();
}

if (switchBrushes) {
SetBackground(m_memoryStream != nullptr, false);
} else {
const auto bitmapBrush{Background().as<winrt::ImageBrush>()};
bitmapBrush.Stretch(ResizeModeToStretch(m_resizeMode));
}
}
}

bool ReactImage::ShouldUseCompositionBrush() {
return m_resizeMode == ResizeMode::Repeat;
}

winrt::Stretch ReactImage::ResizeModeToStretch(react::uwp::ResizeMode value) {
switch (value) {
case ResizeMode::Cover:
return winrt::Stretch::UniformToFill;
case ResizeMode::Stretch:
return winrt::Stretch::Fill;
case ResizeMode::Contain:
return winrt::Stretch::Uniform;
default: // ResizeMode::Center
const auto bitmap{Background().as<winrt::ImageBrush>().ImageSource().as<winrt::BitmapImage>()};
if (bitmap.PixelHeight() < m_availableSize.Height && bitmap.PixelWidth() < m_availableSize.Width) {
return winrt::Stretch::None;
} else {
return winrt::Stretch::Uniform;
}
}
}

winrt::fire_and_forget ReactImage::Source(ImageSource source) {
std::string uriString{source.uri};
if (uriString.length() == 0) {
Expand All @@ -65,51 +108,111 @@ winrt::fire_and_forget ReactImage::Source(ImageSource source) {

winrt::Uri uri{Utf8ToUtf16(uriString)};
winrt::hstring scheme{uri.SchemeName()};
bool needsDownload = (scheme == L"http") || (scheme == L"https");
bool needsDownload =
((scheme == L"http") || (scheme == L"https")) && (m_useCompositionBrush || !source.headers.empty());
bool inlineData = scheme == L"data";

// get weak reference before any co_await calls
auto weak_this{get_weak()};

try {
m_imageSource = source;

winrt::InMemoryRandomAccessStream memoryStream;
if (needsDownload) {
memoryStream = co_await GetImageStreamAsync(source);
m_memoryStream = co_await GetImageStreamAsync(source);
} else if (inlineData) {
memoryStream = co_await GetImageInlineDataAsync(source);
m_memoryStream = co_await GetImageInlineDataAsync(source);
}

if (auto strong_this{weak_this.get()}) {
if ((needsDownload || inlineData) && !memoryStream) {
if ((needsDownload || inlineData) && !strong_this->m_memoryStream) {
strong_this->m_onLoadEndEvent(*strong_this, false);
}

if (!needsDownload || memoryStream) {
auto surface = needsDownload || inlineData ? winrt::LoadedImageSurface::StartLoadFromStream(memoryStream)
: winrt::LoadedImageSurface::StartLoadFromUri(uri);

strong_this->m_surfaceLoadedRevoker = surface.LoadCompleted(
winrt::auto_revoke,
[weak_this, surface](
winrt::LoadedImageSurface const & /*sender*/,
winrt::LoadedImageSourceLoadCompletedEventArgs const &args) {
if (auto strong_this{weak_this.get()}) {
bool succeeded{false};
if (args.Status() == winrt::LoadedImageSourceLoadStatus::Success) {
strong_this->m_brush->Source(surface);
succeeded = true;
}
strong_this->m_onLoadEndEvent(*strong_this, succeeded);
}
});
if (!needsDownload || strong_this->m_memoryStream) {
strong_this->SetBackground(needsDownload || inlineData, true);
}
}
} catch (winrt::hresult_error const &) {
if (auto strong_this{weak_this.get()})
if (auto strong_this{weak_this.get()}) {
strong_this->m_onLoadEndEvent(*strong_this, false);
}
}
} // namespace uwp
}

void ReactImage::SetBackground(bool fromStream, bool fireLoadEndEvent) {
const winrt::Uri uri{Utf8ToUtf16(m_imageSource.uri)};

if (m_useCompositionBrush) {
const auto compositionBrush = ReactImageBrush::Create();
compositionBrush->AvailableSize(m_availableSize);
compositionBrush->ResizeMode(m_resizeMode);

auto surface = fromStream ? winrt::LoadedImageSurface::StartLoadFromStream(m_memoryStream)
: winrt::LoadedImageSurface::StartLoadFromUri(uri);

m_surfaceLoadedRevoker = surface.LoadCompleted(
winrt::auto_revoke,
[weak_this{get_weak()}, compositionBrush, surface, fireLoadEndEvent](
winrt::LoadedImageSurface const & /*sender*/, winrt::LoadedImageSourceLoadCompletedEventArgs const &args) {
if (auto strong_this{weak_this.get()}) {
bool succeeded{false};
if (args.Status() == winrt::LoadedImageSourceLoadStatus::Success) {
winrt::Size dipsSize{surface.DecodedSize()};
strong_this->m_imageSource.height = dipsSize.Height;
strong_this->m_imageSource.width = dipsSize.Width;

compositionBrush->Source(surface);
strong_this->Background(compositionBrush.as<winrt::XamlCompositionBrushBase>());
succeeded = true;
}

if (fireLoadEndEvent) {
strong_this->m_onLoadEndEvent(*strong_this, succeeded);
}
}
});

} else {
winrt::ImageBrush bitmapBrush{};

// ImageOpened and ImageFailed are mutually exclusive. One event of the other will
// always fire whenever an ImageBrush has the ImageSource value set or reset.
m_imageOpenedRevoker = bitmapBrush.ImageOpened(
winrt::auto_revoke, [weak_this{get_weak()}, bitmapBrush, fireLoadEndEvent](const auto &, const auto &) {
if (auto strong_this{weak_this.get()}) {
const auto bitmap{bitmapBrush.ImageSource().as<winrt::BitmapImage>()};
strong_this->m_imageSource.height = bitmap.PixelHeight();
strong_this->m_imageSource.width = bitmap.PixelWidth();

bitmapBrush.Stretch(strong_this->ResizeModeToStretch(strong_this->m_resizeMode));

if (fireLoadEndEvent) {
strong_this->m_onLoadEndEvent(*strong_this, true);
}
}
});

m_imageFailedRevoker = bitmapBrush.ImageFailed(
winrt::auto_revoke, [weak_this{get_weak()}, fireLoadEndEvent](const auto &, const auto &) {
const auto strong_this{weak_this.get()};
if (strong_this && fireLoadEndEvent) {
strong_this->m_onLoadEndEvent(*strong_this, false);
}
});

winrt::BitmapImage bitmap{};

if (fromStream) {
bitmap.SetSourceAsync(m_memoryStream);
} else {
bitmap.UriSource(uri);
}

bitmapBrush.ImageSource(bitmap);
Background(bitmapBrush);
}
}

winrt::IAsyncOperation<winrt::InMemoryRandomAccessStream> GetImageStreamAsync(ImageSource source) {
try {
Expand Down
21 changes: 15 additions & 6 deletions vnext/ReactUWP/Views/Image/ReactImage.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Media.Imaging.h>
#include <winrt/Windows.UI.Xaml.Media.h>
#include <winrt/Windows.UI.Xaml.h>

Expand All @@ -30,7 +31,7 @@ struct ImageSource {
struct ReactImage : winrt::Windows::UI::Xaml::Controls::CanvasT<ReactImage> {
using Super = winrt::Windows::UI::Xaml::Controls::CanvasT<ReactImage>;

ReactImage();
ReactImage() = default;

public:
static winrt::com_ptr<ReactImage> Create();
Expand All @@ -49,17 +50,25 @@ struct ReactImage : winrt::Windows::UI::Xaml::Controls::CanvasT<ReactImage> {
winrt::fire_and_forget Source(ImageSource source);

react::uwp::ResizeMode ResizeMode() {
return m_brush->ResizeMode();
}
void ResizeMode(react::uwp::ResizeMode value) {
m_brush->ResizeMode(value);
return m_resizeMode;
}
void ResizeMode(react::uwp::ResizeMode value);

private:
bool ShouldUseCompositionBrush();
winrt::Windows::UI::Xaml::Media::Stretch ResizeModeToStretch(react::uwp::ResizeMode value);
void SetBackground(bool fromStream, bool fireLoadEndEvent);

bool m_useCompositionBrush{false};
ImageSource m_imageSource;
winrt::com_ptr<ReactImageBrush> m_brush;
winrt::Windows::Foundation::Size m_availableSize{};
react::uwp::ResizeMode m_resizeMode{ResizeMode::Contain};
winrt::Windows::Storage::Streams::InMemoryRandomAccessStream m_memoryStream{nullptr};

winrt::event<winrt::Windows::Foundation::EventHandler<bool>> m_onLoadEndEvent;
winrt::Windows::UI::Xaml::Media::LoadedImageSurface::LoadCompleted_revoker m_surfaceLoadedRevoker;
winrt::Windows::UI::Xaml::Media::ImageBrush::ImageOpened_revoker m_imageOpenedRevoker;
winrt::Windows::UI::Xaml::Media::ImageBrush::ImageFailed_revoker m_imageFailedRevoker;
};

// Helper functions
Expand Down
10 changes: 4 additions & 6 deletions vnext/ReactUWP/Views/Image/ReactImageBrush.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,13 @@ void ReactImageBrush::UpdateCompositionBrush() {

auto compositionBrush{surfaceBrush.as<winrt::CompositionBrush>()};
if (ResizeMode() == ResizeMode::Repeat) {
// If ResizeMode is set to Repeat, then we need to use a
// CompositionEffectBrush. The CompositionSurfaceBrush holding the image
// is used as its source.
// If ResizeMode is set to Repeat, then we need to use a CompositionEffectBrush.
// The CompositionSurfaceBrush holding the image is used as its source.
compositionBrush = GetOrCreateEffectBrush(surfaceBrush);
}

// The CompositionBrush is only set after the image is first loaded,
// and anytime we switch between Surface and Effect brushes (to/from
// ResizeMode::Repeat)
// The CompositionBrush is only set after the image is first loaded and anytime
// we switch between Surface and Effect brushes (to/from ResizeMode::Repeat)
if (CompositionBrush() != compositionBrush) {
if (ResizeMode() == ResizeMode::Repeat) {
surfaceBrush.HorizontalAlignmentRatio(0.0f);
Expand Down