Skip to content

Commit

Permalink
feat(imageBrush): [iOS][macOS] Add support of WriteableBitmap
Browse files Browse the repository at this point in the history
  • Loading branch information
dr1rrb committed Jun 10, 2020
1 parent 3fdfe68 commit 08716bb
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 90 deletions.
5 changes: 4 additions & 1 deletion src/Uno.UI/UI/Xaml/Controls/Border/Border.iOSmacOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ private void UpdateBorderLayer(_Image backgroundImage = null)
{
if (IsLoaded)
{
backgroundImage = backgroundImage ?? (Background as ImageBrush)?.ImageSource?.ImageData;
if (backgroundImage == null)
{
(Background as ImageBrush)?.ImageSource?.TryOpenSync(out backgroundImage);
}

BoundsPath = _borderRenderer.UpdateLayer(
this,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ private static IDisposable InnerCreateLayer(UIElement owner, CALayer parent, Lay
}
else if (background is ImageBrush imgBackground)
{
var uiImage = imgBackground.ImageSource?.ImageData;
if (uiImage != null && uiImage.Size != CGSize.Empty)
var imgSrc = imgBackground.ImageSource;
if (imgSrc != null && imgSrc.TryOpenSync(out var uiImage) && uiImage.Size != CGSize.Empty)
{
var fillMask = new CAShapeLayer()
{
Expand Down Expand Up @@ -207,8 +207,8 @@ private static IDisposable InnerCreateLayer(UIElement owner, CALayer parent, Lay
}
else if (background is ImageBrush imgBackground)
{
var uiImage = imgBackground.ImageSource?.ImageData;
if (uiImage != null && uiImage.Size != CGSize.Empty)
var bgSrc = imgBackground.ImageSource;
if (bgSrc != null && bgSrc.TryOpenSync(out var uiImage) && uiImage.Size != CGSize.Empty)
{
var fullArea = new CGRect(
area.X + borderThickness.Left,
Expand Down Expand Up @@ -346,7 +346,7 @@ private static CGPath GetRoundedPath(CornerRadius cornerRadius, CGRect area)
/// <param name="fillMask">Optional mask layer (for when we use rounded corners)</param>
private static void CreateImageBrushLayers(CGRect fullArea, CGRect insideArea, CALayer layer, List<CALayer> sublayers, ref int insertionIndex, ImageBrush imageBrush, CAShapeLayer fillMask)
{
var uiImage = imageBrush.ImageSource.ImageData;
imageBrush.ImageSource.TryOpenSync(out var uiImage);

// This layer is the one we apply the mask on. It's the full size of the shape because the mask is as well.
var imageContainerLayer = new CALayer
Expand Down
54 changes: 3 additions & 51 deletions src/Uno.UI/UI/Xaml/Controls/Image/Image.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,19 +94,15 @@ private void TryOpenImage()
{
_openedImage = Source;

if (_openedImage is WriteableBitmap writeableBitmap)
{
SetImageFromWriteableBitmap(writeableBitmap);
}
else if (_openedImage == null || !_openedImage.HasSource())
if (_openedImage == null || !_openedImage.HasSource())
{
Image = null;
SetNeedsLayoutOrDisplay();
_imageFetchDisposable.Disposable = null;
}
else if (_openedImage.ImageData != null)
else if (_openedImage.TryOpenSync(out var img))
{
SetImage(_openedImage.ImageData);
SetImage(img);
_imageFetchDisposable.Disposable = null;
}
else
Expand Down Expand Up @@ -146,50 +142,6 @@ private void TryOpenImage()
}
}

private void SetImageFromWriteableBitmap(WriteableBitmap writeableBitmap)
{
if (writeableBitmap.PixelBuffer is InMemoryBuffer memoryBuffer)
{
// Convert RGB colorspace.
var bgraBuffer = memoryBuffer.Data;
var rgbaBuffer = new byte[memoryBuffer.Data.Length];

for (int i = 0; i < memoryBuffer.Data.Length; i += 4)
{
rgbaBuffer[i + 3] = bgraBuffer[i + 3]; // a
rgbaBuffer[i + 0] = bgraBuffer[i + 2]; // r
rgbaBuffer[i + 1] = bgraBuffer[i + 1]; // g
rgbaBuffer[i + 2] = bgraBuffer[i + 0]; // b
}

using (var dataProvider = new CGDataProvider(rgbaBuffer, 0, rgbaBuffer.Length))
{
using (var colorSpace = CGColorSpace.CreateDeviceRGB())
{
var bitsPerComponent = 8;
var bytesPerPixel = 4;

using (var cgImage = new CGImage(
writeableBitmap.PixelWidth,
writeableBitmap.PixelHeight,
bitsPerComponent,
bitsPerComponent * bytesPerPixel,
bytesPerPixel * writeableBitmap.PixelWidth,
colorSpace,
CGImageAlphaInfo.Last,
dataProvider,
null,
false,
CGColorRenderingIntent.Default
))
{
SetImage(UIImage.FromImage(cgImage));
}
}
}
}
}

private void SetImage(UIImage image)
{
using (
Expand Down
5 changes: 4 additions & 1 deletion src/Uno.UI/UI/Xaml/Controls/Panel/Panel.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ private void UpdateBackground(UIImage backgroundImage = null)
// Checking for Window avoids re-creating the layer until it is actually used.
if (IsLoaded)
{
backgroundImage = backgroundImage ?? (Background as ImageBrush)?.ImageSource?.ImageData;
if (backgroundImage == null)
{
(Background as ImageBrush)?.ImageSource?.TryOpenSync(out backgroundImage);
}

_borderRenderer.UpdateLayer(
this,
Expand Down
9 changes: 4 additions & 5 deletions src/Uno.UI/UI/Xaml/Media/ImageBrush.iOSmacOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ public partial class ImageBrush
oldValue?.Dispose();
_imageScheduler.Disposable = null;
}
else if (newValue.ImageData != null)
else if (newValue.TryOpenSync(out var image))
{
SetImage(newValue.ImageData);
SetImage(image);
oldValue?.Dispose();
_imageScheduler.Disposable = null;
}
Expand All @@ -52,11 +52,10 @@ private async void OpenImageAsync(ImageSource newValue)
{
CoreDispatcher.CheckThreadAccess();

var cd = new CancellationDisposable();

using var cd = new CancellationDisposable();
_imageScheduler.Disposable = cd;

var image = await Task.Run(() => newValue.Open(cd.Token));
var image = await Task.Run(() => newValue.Open(cd.Token), cd.Token);

if (cd.Token.IsCancellationRequested)
{
Expand Down
96 changes: 75 additions & 21 deletions src/Uno.UI/UI/Xaml/Media/ImageSource.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ protected ImageSource()
protected ImageSource(UIImage image)
{
_isOriginalSourceUIImage = true;
ImageData = image;
_imageData = image;
}

internal UIImage ImageData { get; private set; }
private UIImage _imageData;

internal string BundleName { get; private set; }
internal string BundlePath { get; private set; }

Expand All @@ -64,10 +65,11 @@ protected ImageSource(UIImage image)

public bool HasSource()
{
return Stream != null
return IsSourceReady
|| Stream != null
|| WebUri != null
|| FilePath.HasValueTrimmed()
|| ImageData != null
|| _imageData != null
|| HasBundle;
}

Expand All @@ -82,14 +84,14 @@ public bool HasSource()
/// </summary>
internal UIImage OpenBundle()
{
ImageData = OpenBundleFromString(BundleName) ?? OpenBundleFromString(BundlePath);
_imageData = OpenBundleFromString(BundleName) ?? OpenBundleFromString(BundlePath);

if (ImageData == null)
if (_imageData == null)
{
this.Log().ErrorFormat("Unable to locate bundle resource [{0}]", BundlePath ?? BundleName);
}

return ImageData;
return _imageData;
}

private static UIImage OpenBundleFromString(string bundle)
Expand All @@ -102,6 +104,48 @@ private static UIImage OpenBundleFromString(string bundle)
return null;
}

/// <summary>
/// Indicates that this ImageSource has enough information to be opened
/// </summary>
private protected virtual bool IsSourceReady => false;

private protected virtual bool TryOpenSourceSync(out UIImage image)
{
image = default;
return false;
}

private protected virtual bool TryOpenSourceAsync(out Task<UIImage> asyncImage)
{
asyncImage = default;
return false;
}

/// <summary>
/// Retrieves the already loaded image, or for supported source (eg. WriteableBitmap, cf remarks),
/// create a native image from the data in memory.
/// </summary>
/// <remarks>
/// This is only intended to convert **uncompressed data already in memory**,
/// and should not be used to decompress a JPEG for instance, even if the already in memory.
/// </remarks>
internal bool TryOpenSync(out UIImage image)
{
if (_imageData != null)
{
image = _imageData;
return true;
}

if (IsSourceReady && TryOpenSourceSync(out image))
{
return true;
}

image = default;
return false;
}

internal async Task<UIImage> Open(CancellationToken ct)
{
using (
Expand All @@ -117,38 +161,48 @@ internal async Task<UIImage> Open(CancellationToken ct)
return null;
}

if (IsSourceReady && TryOpenSourceSync(out var img))
{
return _imageData = img;
}

if (IsSourceReady && TryOpenSourceAsync(out var asyncImg))
{
return _imageData = await asyncImg;
}

if (Stream != null)
{
Stream.Position = 0;
using (var data = NSData.FromStream(Stream))
{
return ImageData = UIImage.LoadFromData(data);
return _imageData = UIImage.LoadFromData(data);
}
}

if (FilePath.HasValue())
{
var uri = new Uri(FilePath);
return ImageData = UIImage.FromFile(FilePath);
return _imageData = UIImage.FromFile(FilePath);
}

if (HasBundle)
{
if (SupportsAsyncFromBundle)
{
return ImageData = OpenBundle();
return _imageData = OpenBundle();
}
else
{
await CoreDispatcher.Main.RunAsync(
CoreDispatcherPriority.Normal,
async () =>
{
ImageData = OpenBundle();
_imageData = OpenBundle();
}
);

return ImageData;
return _imageData;
}
}

Expand All @@ -168,7 +222,7 @@ internal async Task<UIImage> Open(CancellationToken ct)
if (SupportsAsyncFromBundle)
{
// Since iOS9, UIImage.FromBundle is thread safe.
ImageData = UIImage.FromBundle(localFileUri.LocalPath);
_imageData = UIImage.FromBundle(localFileUri.LocalPath);
}
else
{
Expand All @@ -181,19 +235,19 @@ internal async Task<UIImage> Open(CancellationToken ct)
// FromBundle calls UIImage:fromName which caches the decoded image.
// This is done to avoid decoding images from the byte array returned
// from the cache.
ImageData = UIImage.FromBundle(localFileUri.LocalPath);
_imageData = UIImage.FromBundle(localFileUri.LocalPath);
}
else
{
// On iOS 7, FromBundle doesn't work. We can use this method instead.
// Note that we must use OriginalString and not LocalPath.
ImageData = UIImage.LoadFromData(NSData.FromUrl(new NSUrl(localFileUri.OriginalString)));
_imageData = UIImage.LoadFromData(NSData.FromUrl(new NSUrl(localFileUri.OriginalString)));
}
}).AsTask(ct);
}
}

return ImageData;
return _imageData;
}
}

Expand Down Expand Up @@ -235,7 +289,7 @@ private async Task OpenUsingPlatformDownloader(CancellationToken ct)
}
else
{
ImageData = UIImage.LoadFromData(result.Data);
_imageData = UIImage.LoadFromData(result.Data);
}
}
catch (NSErrorException e)
Expand Down Expand Up @@ -301,16 +355,16 @@ internal void UnloadImageData()

private void DisposeUIImage()
{
if (ImageData != null)
if (_imageData != null)
{
ImageData.Dispose();
ImageData = null;
_imageData.Dispose();
_imageData = null;
}
}

public override string ToString()
{
var source = Stream ?? WebUri ?? FilePath ?? (object)ImageData ?? BundlePath ?? BundleName ?? "[No source]";
var source = Stream ?? WebUri ?? FilePath ?? (object)_imageData ?? BundlePath ?? BundleName ?? "[No source]";
return "ImageSource: {0}".InvariantCultureFormat(source);
}
}
Expand Down
Loading

0 comments on commit 08716bb

Please sign in to comment.