winrt::fire_and_forget ReactImage::SetBackground()

in vnext/Microsoft.ReactNative/Views/Image/ReactImage.cpp [212:409]


winrt::fire_and_forget ReactImage::SetBackground(bool fireLoadEndEvent) {
  const ReactImageSource source{m_imageSource};
  winrt::Uri uri{UriTryCreate(Utf8ToUtf16(source.uri))};

  // Increment the image source ID before any co_await calls
  auto currentImageSourceId = ++m_imageSourceId;

  const bool fromStream{
      source.sourceType == ImageSourceType::Download || source.sourceType == ImageSourceType::InlineData};

  winrt::InMemoryRandomAccessStream memoryStream{nullptr};

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

  try {
    memoryStream = co_await GetImageMemoryStreamAsync(source);

    // 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);
      }
      co_return;
    }
  } catch (winrt::hresult_error const &) {
    const auto strong_this{weak_this.get()};
    if (strong_this && fireLoadEndEvent) {
      strong_this->m_onLoadEndEvent(*strong_this, false);
    }
    co_return;
  }

  if (auto strong_this{weak_this.get()}) {
    // If the image source has been updated since this operation started, do not continue
    if (currentImageSourceId != strong_this->m_imageSourceId) {
      co_return;
    }

    if (strong_this->m_useCompositionBrush) {
      const auto compositionBrush{ReactImageBrush::Create()};

      compositionBrush->BlurRadius(strong_this->m_blurRadius);
      compositionBrush->TintColor(strong_this->m_tintColor);

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

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

      if (strong_this->m_surfaceLoadedRevoker) {
        strong_this->m_surfaceLoadedRevoker.revoke();
      }

      if (surface) {
        strong_this->m_surfaceLoadedRevoker = surface.LoadCompleted(
            winrt::auto_revoke,
            [weak_this, compositionBrush, surface, fireLoadEndEvent, uri](
                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 ActualWidth/Height in that case.
                  if (compositionBrush->AvailableSize() == winrt::Size{0, 0}) {
                    compositionBrush->AvailableSize(
                        {static_cast<float>(strong_this->ActualWidth()),
                         static_cast<float>(strong_this->ActualHeight())});
                  }

                  compositionBrush->Source(surface);
                  compositionBrush->ResizeMode(strong_this->m_resizeMode);
                  compositionBrush->BlurRadius(strong_this->m_blurRadius);
                  compositionBrush->TintColor(strong_this->m_tintColor);

                  strong_this->Background(compositionBrush.as<winrt::XamlCompositionBrushBase>());
                  succeeded = true;
                } else {
                  ImageFailed(uri, 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{};
        imageBrush.Stretch(strong_this->ResizeModeToStretch());
      }

      if (source.sourceFormat == ImageSourceFormat::Svg) {
        winrt::SvgImageSource svgImageSource{imageBrush.ImageSource().try_as<winrt::SvgImageSource>()};

        if (!svgImageSource) {
          svgImageSource = winrt::SvgImageSource{};

          strong_this->m_svgImageSourceOpenedRevoker =
              svgImageSource.Opened(winrt::auto_revoke, [weak_this, fireLoadEndEvent](const auto &, const auto &) {
                auto strong_this{weak_this.get()};
                if (strong_this && fireLoadEndEvent) {
                  strong_this->m_onLoadEndEvent(*strong_this, true);
                }
              });

          strong_this->m_svgImageSourceOpenFailedRevoker = svgImageSource.OpenFailed(
              winrt::auto_revoke, [weak_this, fireLoadEndEvent, svgImageSource](const auto &, const auto &args) {
                auto strong_this{weak_this.get()};
                if (strong_this && fireLoadEndEvent) {
                  strong_this->m_onLoadEndEvent(*strong_this, false);
                }
                ImageFailed(svgImageSource, args);
              });

          imageBrush.ImageSource(svgImageSource);
        }

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

      } else {
        winrt::BitmapImage bitmapImage{imageBrush.ImageSource().try_as<winrt::BitmapImage>()};

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

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

                auto strong_this{weak_this.get()};
                if (strong_this && fireLoadEndEvent) {
                  if (auto bitmap{imageBrush.ImageSource().try_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_onLoadEndEvent(*strong_this, true);
                }
              });

          strong_this->m_bitmapImageFailed = bitmapImage.ImageFailed(
              winrt::auto_revoke,
              [imageBrush, weak_this, fireLoadEndEvent, bitmapImage](const auto &, const auto &args) {
                imageBrush.Opacity(1);

                auto strong_this{weak_this.get()};
                if (strong_this && fireLoadEndEvent) {
                  strong_this->m_onLoadEndEvent(*strong_this, false);
                }
                ImageFailed(bitmapImage, args);
              });

          imageBrush.ImageSource(bitmapImage);
        }

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

          // It is possible that the same URI will be set twice if an intermediate update occurs that requires
          // asynchronous behavior (e.g., when the ImageSourceType is ::Download or ::InlineData). In this case,
          // do not set the opacity to zero as the ImageOpened event will not fire.
          auto currentUri = bitmapImage.UriSource();
          if (uri && currentUri && uri.AbsoluteUri() != currentUri.AbsoluteUri()) {
            // TODO: When we change the source of a BitmapImage, we're getting a flicker of the old image
            // being resized to the size of the new image. This is a temporary workaround.
            imageBrush.Opacity(0);
          }
        }
      }

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