Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
112 changes: 112 additions & 0 deletions ReactWindows/ReactNative.Net46/Modules/Image/BitmapImageHelpers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using System.IO;
using System.Net;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Media.Imaging;
using static System.FormattableString;

namespace ReactNative.Modules.Image
{
static class BitmapImageHelpers
{
public static bool IsBase64Uri(string uri)
{
if (uri == null)
{
throw new ArgumentNullException(nameof(uri));
}

return uri.StartsWith("data:");
}

public static Stream GetStreamAsync(string uri)
{
if (uri == null)
{
throw new ArgumentNullException(nameof(uri));
}

if (IsBase64Uri(uri))
{
var decodedData = Convert.FromBase64String(uri.Substring(uri.IndexOf(",") + 1));
return new MemoryStream(decodedData);
}
else
{
var uriValue = default(Uri);
if (!Uri.TryCreate(uri, UriKind.Absolute, out uriValue))
{
throw new ArgumentOutOfRangeException(nameof(uri), Invariant($"Invalid URI '{uri}' provided."));
}

var streamReference = new StreamReader(WebRequest.Create(uri).GetResponse().GetResponseStream()); //RandomAccessStreamReference.CreateFromUri(uriValue);
return streamReference.BaseStream;
}
}

public static IObservable<ImageStatusEventData> GetStreamLoadObservable(this BitmapImage image)
{
return image.GetOpenedObservable()
.Merge(image.GetFailedObservable(), Scheduler.Default)
.StartWith(new ImageStatusEventData(ImageLoadStatus.OnLoadStart));
}

public static IObservable<ImageStatusEventData> GetUriLoadObservable(this BitmapImage image)
{
return Observable.Merge(
Scheduler.Default,
image.GetDownloadingObservable(),
image.GetOpenedObservable(),
image.GetFailedObservable());
}

private static IObservable<ImageStatusEventData> GetOpenedObservable(this BitmapImage image)
{
return Observable.FromEventPattern<EventHandler, RoutedEventArgs>(
h => image.DownloadCompleted += h,
h => image.DownloadCompleted -= h)
.Select(args =>
{
var bitmapImage = args.Sender as BitmapImage;
if (bitmapImage != null)
{
return new ImageStatusEventData(
ImageLoadStatus.OnLoad,
new ImageMetadata(
image.UriSource?.AbsoluteUri,
image.PixelWidth,
image.PixelHeight));
}
else
{
return new ImageStatusEventData(ImageLoadStatus.OnLoad);
}
})
.Take(1)
.Concat(Observable.Return(new ImageStatusEventData(ImageLoadStatus.OnLoadEnd)));
}

private static IObservable<ImageStatusEventData> GetFailedObservable(this BitmapImage image)
{
return Observable.FromEventPattern<EventHandler<System.Windows.Media.ExceptionEventArgs>, ExceptionRoutedEventArgs>(
h => image.DownloadFailed += h,
h => image.DownloadFailed -= h)
.Select<EventPattern<ExceptionRoutedEventArgs>, ImageStatusEventData>(pattern =>
{
throw new InvalidOperationException(pattern.EventArgs.ErrorException.Message);
});
}

private static IObservable<ImageStatusEventData> GetDownloadingObservable(this BitmapImage image)
{
return Observable.FromEventPattern<EventHandler<DownloadProgressEventArgs>, DownloadProgressChangedEventArgs>(
h => image.DownloadProgress += h,
h => image.DownloadProgress -= h)
.Take(1)
.Select(_ => new ImageStatusEventData(ImageLoadStatus.OnLoadStart));
}
}
}
73 changes: 73 additions & 0 deletions ReactWindows/ReactNative.Net46/Modules/Image/ImageLoaderModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Newtonsoft.Json.Linq;
using ReactNative.Bridge;
using System;
using System.Reactive.Linq;
using System.Windows.Media.Imaging;

namespace ReactNative.Modules.Image
{
class ImageLoaderModule : NativeModuleBase
{
private const string ErrorInvalidUri = "E_INVALID_URI";
private const string ErrorPrefetchFailure = "E_PREFETCH_FAILURE";
private const string ErrorGetSizeFailure = "E_GET_SIZE_FAILURE";

public override string Name
{
get
{
return "ImageLoader";
}
}

[ReactMethod]
public void prefetchImage(string uriString, IPromise promise)
{
promise.Reject(ErrorPrefetchFailure, "Prefetch is not yet implemented.");
}

[ReactMethod]
public void getSize(string uriString, IPromise promise)
{
if (string.IsNullOrEmpty(uriString))
{
promise.Reject(ErrorInvalidUri, "Cannot get the size of an image for an empty URI.");
return;
}

DispatcherHelpers.RunOnDispatcher(async () =>
{
try
{
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
var loadQuery = bitmapImage.GetStreamLoadObservable()
.Where(status => status.LoadStatus == ImageLoadStatus.OnLoadEnd)
.FirstAsync()
.Replay(1);

using (loadQuery.Connect())
{
using (var stream = BitmapImageHelpers.GetStreamAsync(uriString))
{
bitmapImage.StreamSource = stream;
}

await loadQuery;
bitmapImage.EndInit();

promise.Resolve(new JObject
{
{ "width", bitmapImage.PixelWidth },
{ "height", bitmapImage.PixelHeight },
});
}
}
catch (Exception ex)
{
promise.Reject(ErrorGetSizeFailure, ex.Message);
}
});
}
}
}
2 changes: 2 additions & 0 deletions ReactWindows/ReactNative.Net46/ReactNative.Net46.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@
</Compile>
<Compile Include="DevSupport\WebSocketJavaScriptExecutor.cs" />
<Compile Include="Modules\Dialog\DialogModule.cs" />
<Compile Include="Modules\Image\BitmapImageHelpers.cs" />
<Compile Include="Modules\Image\ImageLoaderModule.cs" />
<Compile Include="Modules\Storage\AsyncStorageModule.cs" />
<Compile Include="Modules\Clipboard\ClipboardModule.cs" />
<Compile Include="Modules\Clipboard\ClipboardInstance.cs" />
Expand Down
6 changes: 4 additions & 2 deletions ReactWindows/ReactNative.Net46/Shell/MainReactPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
using ReactNative.Modules.Clipboard;
using ReactNative.Modules.Core;
using ReactNative.Modules.Dialog;
using ReactNative.Modules.Image;
using ReactNative.Modules.I18N;
using ReactNative.Modules.NetInfo;
using ReactNative.Modules.Network;
using ReactNative.Modules.Storage;
using ReactNative.Modules.WebSocket;
using ReactNative.UIManager;
using ReactNative.Views.Image;
using ReactNative.Views.Scroll;
using ReactNative.Views.Text;
using ReactNative.Views.View;
Expand Down Expand Up @@ -37,7 +39,7 @@ public IReadOnlyList<INativeModule> CreateNativeModules(ReactContext reactContex
//new CameraRollManager(reactContext),
new ClipboardModule(),
new DialogModule(reactContext),
//new ImageLoaderModule(),
new ImageLoaderModule(),
new I18NModule(),
//new LauncherModule(reactContext),
//new LocationModule(reactContext),
Expand Down Expand Up @@ -72,7 +74,7 @@ public IReadOnlyList<IViewManager> CreateViewManagers(
return new List<IViewManager>
{
//new ReactFlipViewManager(),
//new ReactImageManager(),
new ReactImageManager(),
//new ReactProgressBarViewManager(),
//new ReactProgressRingViewManager(),
//new ReactPickerManager(),
Expand Down
5 changes: 5 additions & 0 deletions ReactWindows/ReactNative.Shared/ReactNative.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Modules\Dialog\DialogModuleHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Modules\I18N\I18NModule.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Modules\I18N\I18NUtil.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Modules\Image\ImageLoadStatus.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Modules\Image\ImageMetadata.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Modules\Image\ImageStatusEventData.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Modules\Network\DefaultHttpClient.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Modules\Network\HttpContentHeaderData.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Modules\Network\HttpContentHelpers.cs" />
Expand Down Expand Up @@ -200,6 +203,8 @@
<Compile Include="$(MSBuildThisFileDirectory)UIManager\ViewParentManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UIManager\ViewParentManagerExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UIManager\ViewProps.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Views\Image\ReactImageLoadEvent.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Views\Image\ReactImageManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Views\Scroll\ScrollEventType.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Views\Scroll\ScrollEventTypeExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Views\Text\FontStyleHelpers.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
#if WINDOWS_UWP
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
#else
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
#endif

namespace ReactNative.Views.Image
{
Expand Down Expand Up @@ -287,16 +294,25 @@ private async void SetUriFromSingleSource(Border view, string source)
}

var image = new BitmapImage();
#if !WINDOWS_UWP
image.BeginInit();
#endif
if (BitmapImageHelpers.IsBase64Uri(source))
{
disposable.Disposable = image.GetStreamLoadObservable().Subscribe(
status => OnImageStatusUpdate(view, status),
_ => OnImageFailed(view));

#if WINDOWS_UWP
using (var stream = await BitmapImageHelpers.GetStreamAsync(source))
{
await image.SetSourceAsync(stream);
}
#else
using (var stream = BitmapImageHelpers.GetStreamAsync(source))
{
image.StreamSource = stream;
}
#endif
}
else
{
Expand All @@ -307,6 +323,10 @@ private async void SetUriFromSingleSource(Border view, string source)
image.UriSource = new Uri(source);
}

#if !WINDOWS_UWP
image.EndInit();
#endif

imageBrush.ImageSource = image;
}

Expand Down
5 changes: 0 additions & 5 deletions ReactWindows/ReactNative/ReactNative.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,6 @@
<Compile Include="Modules\Dialog\DialogModule.cs" />
<Compile Include="Modules\Image\BitmapImageHelpers.cs" />
<Compile Include="Modules\Image\ImageLoaderModule.cs" />
<Compile Include="Modules\Image\ImageLoadStatus.cs" />
<Compile Include="Modules\Image\ImageMetadata.cs" />
<Compile Include="Modules\Image\ImageStatusEventData.cs" />
<Compile Include="Modules\Launch\LauncherModule.cs" />
<Compile Include="Modules\Location\LocationModule.cs" />
<Compile Include="Modules\NetInfo\DefaultNetworkInformation.cs" />
Expand Down Expand Up @@ -156,8 +153,6 @@
<Compile Include="Views\Slider\ReactSliderManager.cs" />
<Compile Include="Views\Slider\ReactSliderShadowNode.cs" />
<Compile Include="Views\Split\Events\SplitViewClosedEvent.cs" />
<Compile Include="Views\Image\ReactImageLoadEvent.cs" />
<Compile Include="Views\Image\ReactImageManager.cs" />
<Compile Include="Views\Picker\ReactPickerManager.cs" />
<Compile Include="Views\Picker\ReactPickerShadowNode.cs" />
<Compile Include="Views\Split\Events\SplitViewOpenedEvent.cs" />
Expand Down