Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 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
16 changes: 10 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,9 +152,13 @@ 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();

if (sources[0].packagerAsset && sources[0].uri.find("file://") == 0) {
sources[0].uri.replace(0, 7, sources[0].bundleRootPath);
}

auto reactImage{canvas.as<ReactImage>()};

EmitImageEvent(canvas, "topLoadStart", sources[0]);
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
231 changes: 186 additions & 45 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,16 @@ 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);
if (m_useCompositionBrush) {
if (auto brush{Background().try_as<ReactImageBrush>()}) {
brush->AvailableSize(finalSize);
}
}

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

winrt::fire_and_forget ReactImage::Source(ImageSource source) {
std::string uriString{source.uri};
if (uriString.length() == 0) {
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(false);
} else if (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);

if (m_imageSource.height < ActualHeight() && m_imageSource.width < ActualWidth()) {
return winrt::Stretch::None;
} else {
return winrt::Stretch::Uniform;
}
}
}

void ReactImage::Source(ReactImageSource source) {
if (source.uri.length() == 0) {
m_onLoadEndEvent(*this, false);
return;
}

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

winrt::Uri uri{Utf8ToUtf16(uriString)};
winrt::Uri uri{Utf8ToUtf16(m_imageSource.uri)};
winrt::hstring scheme{uri.SchemeName()};
bool needsDownload = (scheme == L"http") || (scheme == L"https");
bool inlineData = scheme == L"data";

if (((scheme == L"http") || (scheme == L"https")) && !m_imageSource.headers.empty()) {
m_imageSource.sourceType = ImageSourceType::Download;
} else if (scheme == L"data") {
m_imageSource.sourceType = ImageSourceType::InlineData;
}

SetBackground(true);
}

winrt::IAsyncOperation<winrt::InMemoryRandomAccessStream> ReactImage::GetImageMemoryStreamAsync(
ReactImageSource source) {
switch (source.sourceType) {
case ImageSourceType::Download:
return co_await GetImageStreamAsync(source);
case ImageSourceType::InlineData:
return co_await GetImageInlineDataAsync(source);
default: // ImageSourceType::Uri
return nullptr;
}
}

winrt::fire_and_forget ReactImage::SetBackground(bool fireLoadEndEvent) {
const ReactImageSource source{m_imageSource};
const winrt::Uri uri{Utf8ToUtf16(source.uri)};
const bool fromStream{source.sourceType != ImageSourceType::Uri};

winrt::InMemoryRandomAccessStream memoryStream{nullptr};

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

try {
m_imageSource = source;
memoryStream = co_await GetImageMemoryStreamAsync(source);

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

if (auto strong_this{weak_this.get()}) {
if ((needsDownload || inlineData) && !memoryStream) {
// Fire failed load event if we're not loading from URI and the memory stream is null.
if (fromStream && !memoryStream) {
if (auto strong_this{weak_this.get()}) {
strong_this->m_onLoadEndEvent(*strong_this, false);
}
return;
}
} catch (winrt::hresult_error const &) {
const auto strong_this{weak_this.get()};
if (strong_this && fireLoadEndEvent) {
strong_this->m_onLoadEndEvent(*strong_this, false);
}
}

if (auto strong_this{weak_this.get()}) {
if (strong_this->m_useCompositionBrush) {
const auto compositionBrush{ReactImageBrush::Create()};
compositionBrush->ResizeMode(strong_this->m_resizeMode);

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

m_sizeChangedRevoker = strong_this->SizeChanged(
winrt::auto_revoke, [compositionBrush](const auto &, const winrt::SizeChangedEventArgs &args) {
compositionBrush->AvailableSize(args.NewSize());
});

strong_this->m_surfaceLoadedRevoker = surface.LoadCompleted(
winrt::auto_revoke,
[weak_this, 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 size{surface.DecodedPhysicalSize()};
strong_this->m_imageSource.height = size.Height;
strong_this->m_imageSource.width = size.Width;

// If we are dynamically switching the resizeMode to 'repeat', then
// the SizeChanged event has already fired and the ReactImageBrush's
// size has not been set. Use ActualSize in that case.
if (compositionBrush->AvailableSize() == winrt::Size{0, 0}) {
compositionBrush->AvailableSize(strong_this->ActualSize());
}

if (!needsDownload || memoryStream) {
auto surface = needsDownload || inlineData ? winrt::LoadedImageSurface::StartLoadFromStream(memoryStream)
: winrt::LoadedImageSurface::StartLoadFromUri(uri);
compositionBrush->Source(surface);
strong_this->Background(compositionBrush.as<winrt::XamlCompositionBrushBase>());
succeeded = true;
}

strong_this->m_surfaceLoadedRevoker = surface.LoadCompleted(
winrt::auto_revoke,
[weak_this, surface](
winrt::LoadedImageSurface const & /*sender*/,
winrt::LoadedImageSourceLoadCompletedEventArgs const &args) {
if (fireLoadEndEvent) {
strong_this->m_onLoadEndEvent(*strong_this, succeeded);
}

strong_this->m_sizeChangedRevoker.revoke();
}
});
} else {
winrt::ImageBrush imageBrush{strong_this->Background().try_as<winrt::ImageBrush>()};
bool createImageBrush{!imageBrush};
if (createImageBrush) {
imageBrush = winrt::ImageBrush{};

// ImageOpened and ImageFailed are mutually exclusive. One event of the other will
// always fire whenever an ImageBrush has the ImageSource value set or reset.
strong_this->m_imageBrushOpenedRevoker = imageBrush.ImageOpened(
winrt::auto_revoke, [weak_this, imageBrush, fireLoadEndEvent](const auto &, const auto &) {
if (auto strong_this{weak_this.get()}) {
bool succeeded{false};
if (args.Status() == winrt::LoadedImageSourceLoadStatus::Success) {
strong_this->m_brush->Source(surface);
succeeded = true;
const auto bitmap{imageBrush.ImageSource().as<winrt::BitmapImage>()};
strong_this->m_imageSource.height = bitmap.PixelHeight();
strong_this->m_imageSource.width = bitmap.PixelWidth();

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

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

strong_this->m_imageBrushFailedRevoker =
imageBrush.ImageFailed(winrt::auto_revoke, [weak_this, 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 bitmapImage{imageBrush.ImageSource().try_as<winrt::BitmapImage>()};

if (!bitmapImage) {
bitmapImage = winrt::BitmapImage{};

strong_this->m_bitmapImageOpened = bitmapImage.ImageOpened(
winrt::auto_revoke, [imageBrush](const auto &, const auto &) { imageBrush.Opacity(1); });

imageBrush.ImageSource(bitmapImage);
}

if (createImageBrush) {
strong_this->Background(imageBrush);
}

if (fromStream) {
co_await bitmapImage.SetSourceAsync(memoryStream);
} else {
bitmapImage.UriSource(uri);

// TODO: When we change the source of a BitmapImage, we're getting a flicker of the old image
Comment thread
marlenecota marked this conversation as resolved.
// being resized to the size of the new image. This is a temporary workaround.
imageBrush.Opacity(0);
}
}
} catch (winrt::hresult_error const &) {
if (auto strong_this{weak_this.get()})
strong_this->m_onLoadEndEvent(*strong_this, false);
}
} // namespace uwp
}

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 +291,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