Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
3 changes: 2 additions & 1 deletion vnext/ReactUWP/Views/Image/ImageViewManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ 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
206 changes: 169 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;

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

return finalSize;
}
Expand All @@ -52,66 +53,197 @@ 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 if (auto bitmapBrush{Background().as<winrt::ImageBrush>()}) {
bitmapBrush.Stretch(ResizeModeToStretch(m_resizeMode));
}
}
}

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
// 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>()};
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) {
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);

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 {
bool setBackground{false};
winrt::ImageBrush bitmapBrush{Background().try_as<winrt::ImageBrush>()};
if (!bitmapBrush) {
bitmapBrush = winrt::ImageBrush{};
setBackground = true;

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

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

try {
winrt::BitmapImage bitmap{bitmapBrush.ImageSource().try_as<winrt::BitmapImage>()};
bool setImageSource{false};
if (!bitmap) {
bitmap = winrt::BitmapImage{};
setImageSource = true;
}

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

if (setImageSource) {
bitmapBrush.ImageSource(bitmap);

auto strong_this{weak_this.get()};
if (setBackground && strong_this) {
strong_this->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 +282,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
Loading