Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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"
}
2 changes: 1 addition & 1 deletion vnext/ReactUWP/Modules/ImageViewManagerModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ winrt::fire_and_forget GetImageSizeAsync(
bool succeeded{false};

try {
ImageSource source;
ReactImageSource source;
source.uri = uriString;

winrt::Uri uri{Microsoft::Common::Unicode::Utf8ToUtf16(uriString)};
Expand Down
12 changes: 6 additions & 6 deletions vnext/ReactUWP/Views/Image/ImageViewManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ using namespace Windows::UI::Xaml::Controls;

// Such code is better to move to a seperate parser layer
template <>
struct json_type_traits<react::uwp::ImageSource> {
static react::uwp::ImageSource parseJson(const folly::dynamic &json) {
react::uwp::ImageSource source;
struct json_type_traits<react::uwp::ReactImageSource> {
static react::uwp::ReactImageSource parseJson(const folly::dynamic &json) {
react::uwp::ReactImageSource source;
for (auto &item : json.items()) {
if (item.first == "uri")
source.uri = item.second.asString();
Expand Down Expand Up @@ -80,7 +80,7 @@ class ImageShadowNode : public ShadowNodeBase {

m_onLoadEndToken = reactImage->OnLoadEnd([imageViewManager{static_cast<ImageViewManager *>(GetViewManager())},
reactImage](const auto &, const bool &succeeded) {
ImageSource source{reactImage->Source()};
ReactImageSource source{reactImage->Source()};

imageViewManager->EmitImageEvent(reactImage.as<winrt::Canvas>(), succeeded ? "topLoad" : "topError", source);
imageViewManager->EmitImageEvent(reactImage.as<winrt::Canvas>(), "topLoadEnd", source);
Expand Down Expand Up @@ -134,7 +134,7 @@ void ImageViewManager::UpdateProperties(ShadowNodeBase *nodeToUpdate, const foll
Super::UpdateProperties(nodeToUpdate, reactDiffMap);
}

void ImageViewManager::EmitImageEvent(winrt::Canvas canvas, const char *eventName, ImageSource &source) {
void ImageViewManager::EmitImageEvent(winrt::Canvas canvas, const char *eventName, ReactImageSource &source) {
auto reactInstance{m_wkReactInstance.lock()};
if (reactInstance == nullptr)
return;
Expand All @@ -152,7 +152,7 @@ void ImageViewManager::setSource(winrt::Canvas canvas, const folly::dynamic &dat
if (instance == nullptr)
return;

auto sources{json_type_traits<std::vector<ImageSource>>::parseJson(data)};
auto sources{json_type_traits<std::vector<ReactImageSource>>::parseJson(data)};
sources[0].bundleRootPath = instance->GetBundleRootPath();

auto reactImage{canvas.as<ReactImage>()};
Expand Down
2 changes: 1 addition & 1 deletion vnext/ReactUWP/Views/Image/ImageViewManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ImageViewManager : public FrameworkElementViewManager {
folly::dynamic GetExportedCustomDirectEventTypeConstants() const override;
folly::dynamic GetNativeProps() const override;
facebook::react::ShadowNode *createShadow() const override;
void EmitImageEvent(winrt::Windows::UI::Xaml::Controls::Canvas canvas, const char *eventName, ImageSource &source);
void EmitImageEvent(winrt::Windows::UI::Xaml::Controls::Canvas canvas, const char *eventName, ReactImageSource &source);

protected:
XamlView CreateViewCore(int64_t tag) override;
Expand Down
190 changes: 153 additions & 37 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;
Comment thread
marlenecota marked this conversation as resolved.
Outdated

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

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

winrt::fire_and_forget ReactImage::Source(ImageSource source) {
void ReactImage::ResizeMode(react::uwp::ResizeMode value) {
if (m_resizeMode != value) {
m_resizeMode = value;

bool shouldUseCompositionBrush{m_resizeMode == ResizeMode::Repeat};
bool switchBrushes{m_useCompositionBrush != shouldUseCompositionBrush};

if (switchBrushes) {
m_useCompositionBrush = shouldUseCompositionBrush;
SetBackground(m_memoryStream != nullptr, false);
} else {
const auto bitmapBrush{Background().as<winrt::ImageBrush>()};
bitmapBrush.Stretch(ResizeModeToStretch(m_resizeMode));
Comment thread
marlenecota marked this conversation as resolved.
}
}
}

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
Comment thread
marlenecota marked this conversation as resolved.
// This function should never be called for the 'repeat' resizeMode case.
// That is handled by the shouldUseCompositionBrush/switchBrushes code path.
assert(value != ResizeMode::Repeat);

const auto bitmap{Background().as<winrt::ImageBrush>().ImageSource().as<winrt::BitmapImage>()};
Comment thread
marlenecota marked this conversation as resolved.
Outdated
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(ReactImageSource source) {
std::string uriString{source.uri};
if (uriString.length() == 0) {
m_onLoadEndEvent(*this, false);
return;
}

m_imageSource = source;
m_memoryStream = nullptr;

if (source.packagerAsset && uriString.find("file://") == 0) {
uriString.replace(0, 7, source.bundleRootPath);
}

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) {
Comment thread
marlenecota marked this conversation as resolved.
Outdated
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) {
bool fromStream{needsDownload || inlineData};

// Fire failed load event if we're not loading from URI and the memory stream is null.
if (fromStream && !strong_this->m_memoryStream) {
strong_this->m_onLoadEndEvent(*strong_this, false);
return;
}

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);
}
});
// Verify we're loading from URI or we have a valid memory stream before calling SetBackground.
if (!fromStream || strong_this->m_memoryStream) {
strong_this->SetBackground(fromStream, 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
}

winrt::fire_and_forget 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);
Comment thread
marlenecota marked this conversation as resolved.
Outdated

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();
Comment thread
marlenecota marked this conversation as resolved.
Outdated

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

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

try {
winrt::BitmapImage bitmap{};

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

bitmapBrush.ImageSource(bitmap);
Background(bitmapBrush);
} catch (winrt::hresult_error const &) {
const auto strong_this{weak_this.get()};
if (strong_this && fireLoadEndEvent) {
strong_this->m_onLoadEndEvent(*strong_this, false);
}
}
}
}

winrt::IAsyncOperation<winrt::InMemoryRandomAccessStream> GetImageStreamAsync(ImageSource source) {
winrt::IAsyncOperation<winrt::InMemoryRandomAccessStream> GetImageStreamAsync(ReactImageSource source) {
try {
co_await winrt::resume_background();

Expand Down Expand Up @@ -150,7 +266,7 @@ winrt::IAsyncOperation<winrt::InMemoryRandomAccessStream> GetImageStreamAsync(Im
return nullptr;
}

winrt::IAsyncOperation<winrt::InMemoryRandomAccessStream> GetImageInlineDataAsync(ImageSource source) {
winrt::IAsyncOperation<winrt::InMemoryRandomAccessStream> GetImageInlineDataAsync(ReactImageSource source) {
size_t start = source.uri.find(',');
if (start == std::string::npos || start + 1 > source.uri.length())
return nullptr;
Expand Down
32 changes: 20 additions & 12 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 @@ -16,7 +17,7 @@
namespace react {
namespace uwp {

struct ImageSource {
struct ReactImageSource {
std::string uri;
std::string method;
std::string bundleRootPath;
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 @@ -43,29 +44,36 @@ struct ReactImage : winrt::Windows::UI::Xaml::Controls::CanvasT<ReactImage> {
void OnLoadEnd(winrt::event_token const &token) noexcept;

// Public Properties
ImageSource Source() {
ReactImageSource Source() {
return m_imageSource;
}
winrt::fire_and_forget Source(ImageSource source);
winrt::fire_and_forget Source(ReactImageSource 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:
ImageSource m_imageSource;
winrt::com_ptr<ReactImageBrush> m_brush;
winrt::Windows::UI::Xaml::Media::Stretch ResizeModeToStretch(react::uwp::ResizeMode value);
winrt::fire_and_forget SetBackground(bool fromStream, bool fireLoadEndEvent);

bool m_useCompositionBrush{false};
ReactImageSource m_imageSource;
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
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::Storage::Streams::InMemoryRandomAccessStream>
GetImageStreamAsync(ImageSource source);
GetImageStreamAsync(ReactImageSource source);
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::Storage::Streams::InMemoryRandomAccessStream>
GetImageInlineDataAsync(ImageSource source);
GetImageInlineDataAsync(ReactImageSource source);
} // namespace uwp
} // namespace react
Loading